Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .ci/test-r-package.sh
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ if [[ $OS_NAME == "linux" ]]; then
--no-install-recommends \
-y \
devscripts \
libuv1-dev \
r-base-core=${R_LINUX_VERSION} \
r-base-dev=${R_LINUX_VERSION} \
texinfo \
Expand Down
33 changes: 24 additions & 9 deletions .github/workflows/r_package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -273,15 +273,16 @@ jobs:
# references:
# * CRAN "additional checks": https://cran.r-project.org/web/checks/check_issue_kinds.html
# * images: https://r-hub.github.io/containers/containers.html
#
# Generally, only images that are still supported, listed at
# https://r-hub.github.io/containers/#available-containers, should be used.
image:
- clang16
- clang17
- clang18
- clang19
- clang20
- gcc14
- intel
- clang21
- clang22
- gcc16
- rchk
- ubuntu-clang
- ubuntu-gcc12
runs-on: ubuntu-latest
container: ghcr.io/r-hub/containers/${{ matrix.image }}:latest
steps:
Expand All @@ -302,6 +303,7 @@ jobs:
apt-get install --no-install-recommends -y \
cmake \
devscripts \
libcurl4-openssl-dev \
libuv1-dev \
texinfo \
texlive-latex-extra \
Expand All @@ -315,10 +317,15 @@ jobs:
yum install -y \
cmake \
devscripts \
libcurl-devel \
libuv-devel \
qpdf \
texinfo \
texinfo-tex \
texlive-amsmath \
texlive-amsfonts \
texlive-collection-fontsrecommended \
texlive-collection-latexrecommended \
texlive-latex \
tidy
fi
Expand Down Expand Up @@ -348,8 +355,16 @@ jobs:
fi
fi

# 'testthat' is not needed by 'rchk', so avoid installing it until here
Rscript -e "install.packages('testthat', repos = 'https://cran.rstudio.com', Ncpus = parallel::detectCores())"
# why these packages?
#
# - 'curl' is not a dependency of {lightgbm}, but it's needed for 'R CMD check' URL checks
# - 'testthat' is not needed by 'rchk', so avoid installing it until here
#
Rscript -e "
install.packages(c('curl', 'testthat'),
repos = 'https://cran.rstudio.com',
Ncpus = parallel::detectCores())
"

if [[ "${{ matrix.image }}" =~ "clang" ]]; then
# allowing the following NOTEs (produced by default in the clang images):
Expand Down
46 changes: 46 additions & 0 deletions R-package/tests/testthat/test_basic.R
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,52 @@ test_that("lgb.train() raises an informative error for unrecognized objectives",
}, regexp = "Unknown objective type name: not_a_real_objective")
})

test_that("lgb.train() raises an informative error for unrecognized metrics", {
dtrain <- lgb.Dataset(
data = train$data
, label = train$label
, params = list(num_threads = .LGB_MAX_THREADS)
)
expect_error({
capture.output({
bst <- lgb.train(
data = dtrain
, valids = list(valid = dtrain)
, params = list(
objective = "binary"
, metric = "nonsense"
, verbosity = .LGB_VERBOSITY
, num_threads = .LGB_MAX_THREADS
, num_iterations = 2L
)
)
}, type = "message")
}, regexp = "Unknown metric 'nonsense'")
})

test_that("lgb.cv() raises an informative error for unrecognized metrics", {
dtrain <- lgb.Dataset(
data = train$data
, label = train$label
, params = list(num_threads = .LGB_MAX_THREADS)
)
expect_error({
capture.output({
bst <- lgb.cv(
data = dtrain
, params = list(
objective = "binary"
, metric = "nonsense"
, verbosity = .LGB_VERBOSITY
, num_threads = .LGB_MAX_THREADS
)
, nfold = 2L
, nrounds = 2L
)
}, type = "message")
}, regexp = "Unknown metric 'nonsense'")
})

test_that("lgb.train() respects parameter aliases for objective", {
nrounds <- 3L
dtrain <- lgb.Dataset(
Expand Down
3 changes: 2 additions & 1 deletion python-package/lightgbm/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -3115,7 +3115,8 @@ def set_weight(
# Check if the weight contains values other than one
if weight is not None:
if _is_pyarrow_array(weight):
if pa_compute.all(pa_compute.equal(weight, 1)).as_py():
# TODO: remove 'type: ignore[attr-defined]' when https://github.com/apache/arrow/issues/49831 is resolved.
if pa_compute.all(pa_compute.equal(weight, 1)).as_py(): # type: ignore[attr-defined]
weight = None
elif np.all(weight == 1):
weight = None
Expand Down
6 changes: 6 additions & 0 deletions src/metric/metric.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,12 @@ Metric* Metric::CreateMetric(const std::string& type, const Config& config) {
#ifdef USE_CUDA
}
#endif // USE_CUDA
// "custom" is a sentinel value produced by ParseMetricAlias for "none" / "na" / "null" / "custom";
// it means the user deliberately requested no built-in metric, so returning nullptr is intentional.
// Any other unrecognised name is a user mistake and deserves a clear error.
if (type != std::string("custom")) {
Log::Fatal("Unknown metric '%s'. To avoid enabling LightGBM built-in metrics, pass metric='none'. Otherwise, ensure 'metric' contains only supported values.", type.c_str());
}
return nullptr;
}

Expand Down
36 changes: 36 additions & 0 deletions tests/python_package_test/test_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -4811,6 +4811,42 @@ def test_train_raises_informative_error_for_params_of_wrong_type():
lgb.train(params, dtrain)


def test_train_raises_informative_error_for_unknown_metric():
X, y = load_breast_cancer(return_X_y=True)
dtrain = lgb.Dataset(X, label=y)
params = {"objective": "binary", "metric": "nonsense", "verbose": -1}
with pytest.raises(lgb.basic.LightGBMError, match="Unknown metric 'nonsense'"):
lgb.train(params, dtrain, num_boost_round=1, valid_sets=[dtrain])


@pytest.mark.parametrize(
"metric",
[
"none",
["none", "null"],
["None"],
["NULL"],
],
)
def test_train_sentinel_metric_disables_builtin_metrics(metric):
X, y = load_breast_cancer(return_X_y=True)
dtrain = lgb.Dataset(X, label=y)
evals_result = {}
params = {"objective": "binary", "metric": metric, "verbose": -1}
lgb.train(params, dtrain, num_boost_round=1, valid_sets=[dtrain], callbacks=[lgb.record_evaluation(evals_result)])
assert evals_result == {}


def test_train_with_metric_python_none_uses_default_metric():
X, y = load_breast_cancer(return_X_y=True)
dtrain = lgb.Dataset(X, label=y)
dvalid = lgb.Dataset(X, label=y, reference=dtrain)
evals_result = {}
params = {"objective": "binary", "metric": None, "verbose": -1}
lgb.train(params, dtrain, num_boost_round=1, valid_sets=[dvalid], callbacks=[lgb.record_evaluation(evals_result)])
assert "binary_logloss" in evals_result["valid_0"]


def test_quantized_training():
X, y = make_synthetic_regression()
ds = lgb.Dataset(X, label=y)
Expand Down
21 changes: 19 additions & 2 deletions tests/python_package_test/test_sklearn.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@

SKLEARN_MAJOR, SKLEARN_MINOR, *_ = _sklearn_version.split(".")
SKLEARN_VERSION_GTE_1_6 = (int(SKLEARN_MAJOR), int(SKLEARN_MINOR)) >= (1, 6)
SKLEARN_VERSION_GTE_1_7 = (int(SKLEARN_MAJOR), int(SKLEARN_MINOR)) >= (1, 7)

decreasing_generator = itertools.count(0, -1)
estimator_classes = (lgb.LGBMModel, lgb.LGBMClassifier, lgb.LGBMRegressor, lgb.LGBMRanker)
Expand Down Expand Up @@ -453,7 +454,15 @@ def test_classifier_chain():
X, y = make_multilabel_classification(n_samples=100, n_features=20, n_classes=n_outputs, random_state=0)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=42)
order = [2, 0, 1]
clf = ClassifierChain(base_estimator=lgb.LGBMClassifier(n_estimators=10), order=order, random_state=42)
# 'base_estimator' parameter was deprecated in scikit-learn 1.7 and removed in 1.9
#
# * https://github.com/scikit-learn/scikit-learn/pull/30152
# * https://github.com/scikit-learn/scikit-learn/pull/33750
#
if SKLEARN_VERSION_GTE_1_7:
clf = ClassifierChain(estimator=lgb.LGBMClassifier(n_estimators=10), order=order, random_state=42)
else:
clf = ClassifierChain(base_estimator=lgb.LGBMClassifier(n_estimators=10), order=order, random_state=42)
clf.fit(X_train, y_train)
score = clf.score(X_test, y_test)
assert score >= 0.2
Expand All @@ -470,7 +479,15 @@ def test_regressor_chain():
X, y = bunch["data"], bunch["target"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=42)
order = [2, 0, 1]
reg = RegressorChain(base_estimator=lgb.LGBMRegressor(n_estimators=10), order=order, random_state=42)
# 'base_estimator' parameter was deprecated in scikit-learn 1.7 and removed in 1.9
#
# * https://github.com/scikit-learn/scikit-learn/pull/30152
# * https://github.com/scikit-learn/scikit-learn/pull/33750
#
if SKLEARN_VERSION_GTE_1_7:
reg = RegressorChain(estimator=lgb.LGBMRegressor(n_estimators=10), order=order, random_state=42)
else:
reg = RegressorChain(base_estimator=lgb.LGBMRegressor(n_estimators=10), order=order, random_state=42)
reg.fit(X_train, y_train)
y_pred = reg.predict(X_test)
_, score, _ = mse(y_test, y_pred)
Expand Down