-
Notifications
You must be signed in to change notification settings - Fork 8
RMCB
rmcb(data, level=0.95, outplot=c("mcb","lines","none"), select=NULL, ...)
# Methods on the returned "rmcb" object
print(x, ...)
plot(x, outplot=c("mcb","lines"), select=NULL, ...)The distribution, na.last and ties.method arguments are passed through
the ....
def rmcb(
data,
level: float = 0.95,
outplot: str = "mcb",
select=None,
distribution: str = "tukey",
na_last: bool = True,
ties_method: str = "average",
) -> RMCBResult: ...
# Methods on the returned RMCBResult object
RMCBResult.__str__(self) -> str # print(result)
RMCBResult.plot(self, outplot="mcb", select=None, ax=None, **kwargs)When several forecasting methods are evaluated on the same set of time series, a natural question is which method is best, and which others are statistically indistinguishable from it? RMCB — Regression for Multiple Comparison with the Best — answers this (Demsar, 2006).
The input data has the methods in columns and the series (or
observations) in rows — typically a matrix of an error measure (e.g. one of
the measures) computed for every method on every series. In all cases each
method ends up with a mean value and a confidence interval; methods
whose intervals overlap are statistically indistinguishable and are collected
into groups (used by the "lines" plot and the groups/methods/vlines
fields).
The distribution argument selects one of two families of comparison:
-
Nemenyi / MCB — the default
(
distribution="tukey"): the classical rank-based test. The inference comes from the Studentised-range statistic and the Friedman test, not from a regression. -
Regression for MCB —
(
distribution="dnorm","dlnorm", or any other): a regression-based generalisation that fits a dummy-variable model and takes the standard errors and the significance test from that fitted model.
# R — function is in the greybox namespace
library(greybox)# Python
from greybox import rmcbWith the default distribution="tukey", rmcb() reproduces the classical
Nemenyi test exactly. It is not a regression-based comparison:
- The data is ranked within each row (
ties.method/ties_methodcontrols ties). - Each method's statistic is its mean rank across the series.
- The critical distance — the half-width shared by every interval — is the
Studentised-range statistic:
qtukey(level, k, Inf) · sqrt(k(k+1) / (12·n)) / 2, wherekis the number of methods andnthe number of series. - The overall significance (is any method different from the others?) is the Friedman test p-value.
So the confidence intervals and the p-value come purely from the classical Nemenyi / Friedman machinery; no regression standard errors are used. (Internally the mean ranks happen to be computed as the coefficients of a dummy regression on the ranks — they equal the column means of the ranks — but that regression's standard errors and F-test play no part in the default result.)
For any non-default distribution, rmcb() lives up to its name: it fits a
dummy-variable regression and derives the whole comparison from that fitted
model:
y = b' X + e
X is treatment-coded with one dummy per method (the intercept is the first
method), so the coefficients b give each method's location. The interval
half-width is taken from the model's standard errors,
qt((1+level)/2, df) · SE, and the significance is a regression-based test:
-
"dnorm"— ranks the data, then fits an OLS regression on the ranks; p-value from the regression F-test. On large datasets this is close to the default Nemenyi test. -
"dlnorm"— fits OLS onlog(data)(raw, positive data); the coefficients and intervals are exponentiated; chi-squared (log-likelihood-ratio) p-value. -
any other (e.g.
"dlaplace","ds", …) — fits an ALM with that distribution on the raw data, comparing the methods' locations; chi-squared p-value.
distribution |
Approach | Data used | Critical distance | Significance p-value |
|---|---|---|---|---|
"tukey" (default) |
Nemenyi / MCB | ranks | Studentised range: qtukey(level, k, Inf) · sqrt(k(k+1) / (12·n)) / 2
|
Friedman test → classical Nemenyi test |
"dnorm" |
Regression for MCB | ranks |
qt((1+level)/2, df) · SE from an OLS regression |
regression F-test |
"dlnorm" |
Regression for MCB | raw data |
qt · SE on log(data); coefficients/intervals exponentiated |
chi-squared test (log-likelihood ratio) |
any other (e.g. "dlaplace", "ds", …) |
Regression for MCB | raw data |
qt · SE from alm()/ALM() fitted with that distribution |
chi-squared test |
| Parameter | R | Python | Type | Default | Description |
|---|---|---|---|---|---|
| data | data |
data |
matrix/data.frame / array-like or DataFrame
|
— | Methods in columns, series in rows. Column names become method names. |
| level | level |
level |
numeric/float
|
0.95 |
Width of the confidence intervals. |
| outplot | outplot |
outplot |
character/str
|
"mcb" |
Plot style: "mcb", "lines" or "none". See the interface note below.
|
| select | select |
select |
index / NULL / None
|
NULL/None
|
Method to highlight. NULL/None selects the best (lowest-mean) method. |
| distribution | ... |
distribution |
character/str
|
"tukey" |
Test variant (see table above). |
| na.last / na_last | ... |
na_last |
logical/bool
|
TRUE/True
|
Whether NA/NaN are ranked last. |
| ties.method / ties_method | ... |
ties_method |
character/str
|
"average" |
Tie-handling for the ranks. |
Interface difference. In R,
rmcb()draws the plot as a side effect unlessoutplot="none". The Pythonrmcb()never auto-plots — it only computes and returns the result; callresult.plot(...)to draw. Theoutplotvalue is stored and used as the default style for that method. See R-Python-differences.
R returns an S3 object of class "rmcb"; Python returns an RMCBResult. Both
expose the same fields:
| Field | R | Python | Type | Description |
|---|---|---|---|---|
| mean | $mean |
.mean |
named vector / pd.Series
|
Mean values (mean ranks for "tukey"/"dnorm") per method, sorted ascending. |
| interval | $interval |
.interval |
matrix / pd.DataFrame
|
Lower/upper confidence bounds per method. |
| vlines | $vlines |
.vlines |
matrix / pd.DataFrame
|
Group start/end positions used by the "lines" plot. |
| groups | $groups |
.groups |
logical matrix / pd.DataFrame
|
TRUE/True if a method belongs to a group. |
| methods | $methods |
.methods |
logical matrix / pd.DataFrame
|
Pairwise interval-overlap table. |
| p.value | $p.value |
.p_value |
numeric / float
|
Significance test p-value. |
| level | $level |
.level |
numeric / float
|
Confidence level. |
| model | $model |
.model |
model object | Fitted model used for the intervals. |
| select | $select |
.select |
integer |
1-based position (in sorted order) of the highlighted method. |
| distribution | — | .distribution |
str |
The distribution used (Python convenience field). |
The example mirrors ?rmcb: four methods with deliberately shifted locations,
so method A should rank best.
library(greybox)
N <- 50
M <- 4
ourData <- matrix(rnorm(N*M, mean=0, sd=1), N, M)
ourData[,2] <- ourData[,2]+4
ourData[,3] <- ourData[,3]+3
ourData[,4] <- ourData[,4]+2
colnames(ourData) <- c("Method A","Method B","Method C - long name","Method D")
ourTest <- rmcb(ourData, level=0.95)
# Mean ranks and intervals
ourTest$mean
ourTest$interval
# Plots
plot(ourTest) # "mcb" style (default)
plot(ourTest, outplot="lines") # grouped "lines" styleimport numpy as np
import pandas as pd
from greybox import rmcb
rng = np.random.default_rng(42)
data = rng.normal(size=(50, 4))
data[:, 1] += 4
data[:, 2] += 3
data[:, 3] += 2
df = pd.DataFrame(
data,
columns=["Method A", "Method B", "Method C - long name", "Method D"],
)
result = rmcb(df, level=0.95)
print(result)
# Regression for Multiple Comparison with the Best
# The significance level is 5%
# The number of observations is 50, the number of methods is 4
# Significance test p-value: 0.0
print(result.mean.round(4))
# Method A 1.04
# Method D 2.18
# Method C - long name 3.12
# Method B 3.66
print(result.interval.round(4))
# 2.5% 97.5%
# Method A 0.7083 1.3717
# Method D 1.8483 2.5117
# Method C - long name 2.7883 3.4517
# Method B 3.3283 3.9917
# Plots (Python never auto-plots — call plot() explicitly)
result.plot() # "mcb" style (default)
result.plot(outplot="lines") # grouped "lines" style# R — Student-distribution variant (≈ tukey on large data)
ourTest <- rmcb(ourData, distribution="dnorm")# Python
result = rmcb(df, distribution="dnorm")-
"mcb"(default): one vertical line per method spanning its confidence interval, with a point at the mean rank. The selected method (by default the best, i.e. lowest-mean) is highlighted and its interval bounds are drawn as horizontal dashed reference lines. Any method whose interval crosses those lines overlaps the best method and shares its colour — it is not significantly worse. The title reports the test p-value and the confidence level. -
"lines": each group of statistically indistinguishable methods is drawn as a vertical segment spanning the methods it covers, so overlapping groups are easy to read off. The method names label the vertical axis.
Use select= (a method name or index) to highlight a different reference
method on either plot.
| R | Python |
|---|---|
rmcb() |
rmcb() |
print(x) |
print(result) / str(result)
|
plot(x, outplot=...) |
result.plot(outplot=...) |
x$p.value |
result.p_value |
- Top-level imports:
from greybox import rmcb, RMCBResult. -
matplotlib>=3.5is a hard dependency, soresult.plot()always works. -
Parity:
mean,intervalandp_valuematch R to about1e-8for the rank/OLS branches ("tukey","dnorm","dlnorm"). For the generalalm()-based branch the means and p-values match, but interval widths can differ: R falls back to a bootstrapped covariance matrix when the Hessian is inaccurate, whereas Python uses the Hessian-basedvcov. See R-Python-differences. -
selectis reported as a 1-based position in the sorted order (matching R); the Pythonplot(select=...)additionally accepts a method name or a 0-based column index. - The tabular fields (
interval,vlines,groups,methods) are returned as pandasDataFrames andmeanas aSeries, indexed by method name.
- Demsar, J. (2006). Statistical Comparisons of Classifiers over Multiple Data Sets. Journal of Machine Learning Research, 7, 1-30. https://www.jmlr.org/papers/volume7/demsar06a/demsar06a.pdf
- Svetunkov, I. (2018). greybox 0.3.0 — what's new. https://openforecast.org/2018/08/07/greybox-0-3-0-whats-new/
- See Resources for the full reference list.