Skip to content

Commit a353b7f

Browse files
Stéphane Couvreurthomasjpfan
authored andcommitted
ENH Extension of v_measure_score metric to include beta parameter (scikit-learn#13607)
1 parent e850e7a commit a353b7f

File tree

4 files changed

+68
-6
lines changed

4 files changed

+68
-6
lines changed

doc/modules/clustering.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1277,6 +1277,22 @@ Their harmonic mean called **V-measure** is computed by
12771277
>>> metrics.v_measure_score(labels_true, labels_pred) # doctest: +ELLIPSIS
12781278
0.51...
12791279

1280+
This function's formula is as follows:::
1281+
1282+
.. math:: v = \frac{(1 + \beta) \times \text{homogeneity} \times \text{completeness}}{(\beta \times \text{homogeneity} + \text{completeness})}
1283+
1284+
`beta` defaults to a value of 1.0, but for using a value less than 1 for beta::
1285+
1286+
>>> metrics.v_measure_score(labels_true, labels_pred, beta=0.6) # doctest: +ELLIPSIS
1287+
0.54...
1288+
1289+
more weight will be attributed to homogeneity, and using a value greater than 1::
1290+
1291+
>>> metrics.v_measure_score(labels_true, labels_pred, beta=1.8) # doctest: +ELLIPSIS
1292+
0.48...
1293+
1294+
more weight will be attributed to completeness.
1295+
12801296
The V-measure is actually equivalent to the mutual information (NMI)
12811297
discussed above, with the aggregation function being the arithmetic mean [B2011]_.
12821298

doc/whats_new/v0.21.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,13 @@ Support for Python 3.4 and below has been officially dropped.
409409
:issue:`12334` by :user:`Emmanuel Arias <[email protected]>`,
410410
`Joel Nothman`_ and `Andreas Müller`_
411411

412+
- |Enhancement| Added `beta` parameter to
413+
:func:`metrics.cluster.supervised.homogeneity_completeness_v_measure` and
414+
:func:`metrics.cluster.supervised.v_measure_score` to configure the
415+
tradeoff between homogeneity and completeness.
416+
:issue:`13607` by :user:`Stephane Couvreur <scouvreur>` and
417+
and :user:`Ivan Sanchez <ivsanro1>`.
418+
412419
- |Fix| The metric :func:`metrics.r2_score` is degenerate with a single sample
413420
and now it returns NaN and raises :class:`exceptions.UndefinedMetricWarning`.
414421
:issue:`12855` by :user:`Pawel Sendyk <psendyk>`.

sklearn/metrics/cluster/supervised.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ def adjusted_rand_score(labels_true, labels_pred):
239239
return (sum_comb - prod_comb) / (mean_comb - prod_comb)
240240

241241

242-
def homogeneity_completeness_v_measure(labels_true, labels_pred):
242+
def homogeneity_completeness_v_measure(labels_true, labels_pred, beta=1.0):
243243
"""Compute the homogeneity and completeness and V-Measure scores at once.
244244
245245
Those metrics are based on normalized conditional entropy measures of
@@ -275,6 +275,12 @@ def homogeneity_completeness_v_measure(labels_true, labels_pred):
275275
labels_pred : array, shape = [n_samples]
276276
cluster labels to evaluate
277277
278+
beta : float
279+
Ratio of weight attributed to ``homogeneity`` vs ``completeness``.
280+
If ``beta`` is greater than 1, ``completeness`` is weighted more
281+
strongly in the calculation. If ``beta`` is less than 1,
282+
``homogeneity`` is weighted more strongly.
283+
278284
Returns
279285
-------
280286
homogeneity : float
@@ -309,8 +315,8 @@ def homogeneity_completeness_v_measure(labels_true, labels_pred):
309315
if homogeneity + completeness == 0.0:
310316
v_measure_score = 0.0
311317
else:
312-
v_measure_score = (2.0 * homogeneity * completeness /
313-
(homogeneity + completeness))
318+
v_measure_score = ((1 + beta) * homogeneity * completeness
319+
/ (beta * homogeneity + completeness))
314320

315321
return homogeneity, completeness, v_measure_score
316322

@@ -459,15 +465,16 @@ def completeness_score(labels_true, labels_pred):
459465
return homogeneity_completeness_v_measure(labels_true, labels_pred)[1]
460466

461467

462-
def v_measure_score(labels_true, labels_pred):
468+
def v_measure_score(labels_true, labels_pred, beta=1.0):
463469
"""V-measure cluster labeling given a ground truth.
464470
465471
This score is identical to :func:`normalized_mutual_info_score` with
466472
the ``'arithmetic'`` option for averaging.
467473
468474
The V-measure is the harmonic mean between homogeneity and completeness::
469475
470-
v = 2 * (homogeneity * completeness) / (homogeneity + completeness)
476+
v = (1 + beta) * homogeneity * completeness
477+
/ (beta * homogeneity + completeness)
471478
472479
This metric is independent of the absolute values of the labels:
473480
a permutation of the class or cluster label values won't change the
@@ -489,6 +496,12 @@ def v_measure_score(labels_true, labels_pred):
489496
labels_pred : array, shape = [n_samples]
490497
cluster labels to evaluate
491498
499+
beta : float
500+
Ratio of weight attributed to ``homogeneity`` vs ``completeness``.
501+
If ``beta`` is greater than 1, ``completeness`` is weighted more
502+
strongly in the calculation. If ``beta`` is less than 1,
503+
``homogeneity`` is weighted more strongly.
504+
492505
Returns
493506
-------
494507
v_measure : float
@@ -554,7 +567,8 @@ def v_measure_score(labels_true, labels_pred):
554567
0.0...
555568
556569
"""
557-
return homogeneity_completeness_v_measure(labels_true, labels_pred)[2]
570+
return homogeneity_completeness_v_measure(labels_true, labels_pred,
571+
beta=beta)[2]
558572

559573

560574
def mutual_info_score(labels_true, labels_pred, contingency=None):

sklearn/metrics/cluster/tests/test_supervised.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,31 @@ def test_not_complete_and_not_homogeneous_labeling():
126126
assert_almost_equal(v, 0.52, 2)
127127

128128

129+
def test_beta_parameter():
130+
# test for when beta passed to
131+
# homogeneity_completeness_v_measure
132+
# and v_measure_score
133+
beta_test = 0.2
134+
h_test = 0.67
135+
c_test = 0.42
136+
v_test = ((1 + beta_test) * h_test * c_test
137+
/ (beta_test * h_test + c_test))
138+
139+
h, c, v = homogeneity_completeness_v_measure(
140+
[0, 0, 0, 1, 1, 1],
141+
[0, 1, 0, 1, 2, 2],
142+
beta=beta_test)
143+
assert_almost_equal(h, h_test, 2)
144+
assert_almost_equal(c, c_test, 2)
145+
assert_almost_equal(v, v_test, 2)
146+
147+
v = v_measure_score(
148+
[0, 0, 0, 1, 1, 1],
149+
[0, 1, 0, 1, 2, 2],
150+
beta=beta_test)
151+
assert_almost_equal(v, v_test, 2)
152+
153+
129154
def test_non_consecutive_labels():
130155
# regression tests for labels with gaps
131156
h, c, v = homogeneity_completeness_v_measure(

0 commit comments

Comments
 (0)