web-dev-qa-db-fra.com

Comment réduire une plage de nombres avec une valeur min et max connue

J'essaie donc de comprendre comment utiliser une plage de nombres et de réduire les valeurs pour les adapter à une plage. La raison de vouloir faire cela est que je tente de dessiner des ellipses dans un Java swing jpanel. Je veux que la hauteur et la largeur de chaque ellipse soient comprises entre 1 et 30. J'ai des méthodes qui recherchent les valeurs minimales et maximales à partir de mon ensemble de données, mais je n'ai pas les valeurs min et max avant l'exécution. Y a-t-il un moyen facile de faire ceci?

213
user650271

Supposons que vous souhaitiez mettre à l'échelle une plage [min,max] à [a,b]. Vous recherchez une fonction (continue) qui satisfait

f(min) = a
f(max) = b

Dans votre cas, a serait égal à 1 et b à 30, mais commençons par quelque chose de plus simple et essayons de mapper [min,max] dans la plage [0,1].

Mettre min dans une fonction et sortir de 0 pourrait être accompli avec

f(x) = x - min   ===>   f(min) = min - min = 0

C'est presque ce que nous voulons. Mais ajouter max nous donnerait max - min quand nous en voudrons réellement 1. Il nous faudra donc l'adapter:

        x - min                                  max - min
f(x) = ---------   ===>   f(min) = 0;  f(max) =  --------- = 1
       max - min                                 max - min

c'est ce que nous voulons. Nous devons donc faire une traduction et une mise à l'échelle. Maintenant, si nous voulons plutôt obtenir des valeurs arbitraires de a et b, nous avons besoin de quelque chose d'un peu plus compliqué:

       (b-a)(x - min)
f(x) = --------------  + a
          max - min

Vous pouvez vérifier que l'insertion de min pour x donne maintenant a, et l'insertion de max donne b.

Vous remarquerez peut-être aussi que (b-a)/(max-min) est un facteur d'échelle entre la taille de la nouvelle plage et la taille de la plage d'origine. Nous traduisons donc d'abord x par -min, en l'adaptant au facteur correct, puis en le traduisant à la nouvelle valeur minimale de a.

J'espère que cela t'aides.

480
irritate

Voici du code JavaScript facilitant le copier-coller (voici la réponse à irritate):

function scaleBetween(unscaledNum, minAllowed, maxAllowed, min, max) {
  return (maxAllowed - minAllowed) * (unscaledNum - min) / (max - min) + minAllowed;
}

Appliqué de la sorte, la plage 10-50 est ajustée à une plage comprise entre 0 et 100.

var unscaledNums = [10, 13, 25, 28, 43, 50];

var maxRange = Math.max.apply(Math, unscaledNums);
var minRange = Math.min.apply(Math, unscaledNums);

for (var i = 0; i < unscaledNums.length; i++) {
  var unscaled = unscaledNums[i];
  var scaled = scaleBetween(unscaled, 0, 100, minRange, maxRange);
  console.log(scaled.toFixed(2));
}

0.00, 18.37, 48.98, 55.10, 85.71, 100.00

Edit:

Je sais que j'ai répondu à cela il y a longtemps, mais voici une fonction de nettoyage que j'utilise maintenant:

Array.prototype.scaleBetween = function(scaledMin, scaledMax) {
  var max = Math.max.apply(Math, this);
  var min = Math.min.apply(Math, this);
  return this.map(num => (scaledMax-scaledMin)*(num-min)/(max-min)+scaledMin);
}

Appliqué comme suit:

[-4, 0, 5, 6, 9].scaleBetween(0, 100);

[0, 30.76923076923077, 69.23076923076923, 76.92307692307692, 100]

40
Charles Clayton

Pour plus de commodité, voici l'algorithme d'Irritate sous une forme Java. Ajoutez la vérification des erreurs, la gestion des exceptions et ajustez si nécessaire.

public class Algorithms { 
    public static double scale(final double valueIn, final double baseMin, final double baseMax, final double limitMin, final double limitMax) {
        return ((limitMax - limitMin) * (valueIn - baseMin) / (baseMax - baseMin)) + limitMin;
    }
}

Testeur:

final double baseMin = 0.0;
final double baseMax = 360.0;
final double limitMin = 90.0;
final double limitMax = 270.0;
double valueIn = 0;
System.out.println(Algorithms.scale(valueIn, baseMin, baseMax, limitMin, limitMax));
valueIn = 360;
System.out.println(Algorithms.scale(valueIn, baseMin, baseMax, limitMin, limitMax));
valueIn = 180;
System.out.println(Algorithms.scale(valueIn, baseMin, baseMax, limitMin, limitMax));

90.0
270.0
180.0
26
Java42

Voici comment je le comprends:


Quel est le pourcentage de x dans une plage

Supposons que vous avez une plage de 0 à 100. Étant donné un nombre arbitraire de cette plage, dans quel "pourcentage" se situe-t-il? Cela devrait être assez simple, 0 serait 0%, 50 serait 50% et 100 serait 100%.

Maintenant, que se passe-t-il si votre plage va de 20 à 100? Nous ne pouvons pas appliquer la même logique que ci-dessus (diviser par 100) car:

20 / 100

ne nous donne pas 0 (20 devrait être 0% maintenant). Cela devrait être simple à corriger, il suffit de faire le numérateur 0 pour le cas de 20. Nous pouvons le faire en soustrayant:

(20 - 20) / 100

Cependant, cela ne fonctionne plus pour 100 parce que:

(100 - 20) / 100

ne nous donne pas 100%. Encore une fois, nous pouvons résoudre ce problème en soustrayant également du dénominateur:

(100 - 20) / (100 - 20)

Une équation plus généralisée pour découvrir ce que% x se situe dans une plage serait:

(x - MIN) / (MAX - MIN)

Plage d'échelle à une autre plage

Maintenant que nous connaissons le pourcentage d'un nombre dans une plage, nous pouvons l'appliquer pour mapper le nombre à une autre plage. Passons en exemple.

old range = [200, 1000]
new range = [10, 20]

Si nous avons un nombre dans l'ancienne plage, quel serait le nombre dans la nouvelle plage? Disons que le nombre est 400. D'abord, déterminez quel pourcentage 400 se trouve dans l'ancienne plage. Nous pouvons appliquer notre équation ci-dessus.

(400 - 200) / (1000 - 200) = 0.25

Ainsi, 400 se trouve dans 25% de l'ancienne gamme. Nous avons juste besoin de savoir quel numéro est 25% de la nouvelle gamme. Pensez à ce que 50% de [0, 20] est. Ce serait 10 non? Comment êtes-vous arrivé à cette réponse? Eh bien, nous pouvons simplement faire:

20 * 0.5 = 10

Mais qu'en est-il de [10, 20]? Nous devons tout décaler de 10 maintenant. par exemple:

((20 - 10) * 0.5) + 10

une formule plus généralisée serait:

((MAX - MIN) * PERCENT) + MIN

Pour l'exemple original de ce que 25% de [10, 20] est:

((20 - 10) * 0.25) + 10 = 12.5

Ainsi, 400 dans la plage [200, 1000] mapperait à 12.5 dans la plage [10, 20]


TLDR

Pour mapper x de l'ancienne gamme à la nouvelle:

OLD PERCENT = (x - OLD MIN) / (OLD MAX - OLD MIN)
NEW X = ((NEW MAX - NEW MIN) * OLD PERCENT) + NEW MIN
18
Vic

Je suis tombé sur cette solution, mais cela ne correspond pas vraiment à mes besoins. J'ai donc un peu fouillé dans le code source d3. Personnellement, je recommanderais de le faire comme le fait d3.scale.

Donc, ici, vous adaptez le domaine à la plage. L'avantage est que vous pouvez inverser les signes dans la plage de votre cible. Ceci est utile car l'axe y sur un écran d'ordinateur descend de haut en bas, ainsi les grandes valeurs ont un petit y.

public class Rescale {
    private final double range0,range1,domain0,domain1;

    public Rescale(double domain0, double domain1, double range0, double range1) {
        this.range0 = range0;
        this.range1 = range1;
        this.domain0 = domain0;
        this.domain1 = domain1;
    }

    private double interpolate(double x) {
        return range0 * (1 - x) + range1 * x;
    }

    private double uninterpolate(double x) {
        double b = (domain1 - domain0) != 0 ? domain1 - domain0 : 1 / domain1;
        return (x - domain0) / b;
    }

    public double rescale(double x) {
        return interpolate(uninterpolate(x));
    }
}

Et voici le test où vous pouvez voir ce que je veux dire

public class RescaleTest {

    @Test
    public void testRescale() {
        Rescale r;
        r = new Rescale(5,7,0,1);
        Assert.assertTrue(r.rescale(5) == 0);
        Assert.assertTrue(r.rescale(6) == 0.5);
        Assert.assertTrue(r.rescale(7) == 1);

        r = new Rescale(5,7,1,0);
        Assert.assertTrue(r.rescale(5) == 1);
        Assert.assertTrue(r.rescale(6) == 0.5);
        Assert.assertTrue(r.rescale(7) == 0);

        r = new Rescale(-3,3,0,1);
        Assert.assertTrue(r.rescale(-3) == 0);
        Assert.assertTrue(r.rescale(0) == 0.5);
        Assert.assertTrue(r.rescale(3) == 1);

        r = new Rescale(-3,3,-1,1);
        Assert.assertTrue(r.rescale(-3) == -1);
        Assert.assertTrue(r.rescale(0) == 0);
        Assert.assertTrue(r.rescale(3) == 1);
    }
}
10
KIC

J'ai pris la réponse d'Irritate et la refactorisée afin de minimiser les étapes de calcul pour les calculs ultérieurs en la factorisant dans le moins de constantes possibles. La motivation est de permettre à un mesureur d’être formé sur un ensemble de données, puis d’être exécuté sur de nouvelles données (pour un algorithme ML). En fait, cela ressemble beaucoup au pré-traitement de MinMaxScaler de SciKit pour Python en cours d'utilisation.

Ainsi, x' = (b-a)(x-min)/(max-min) + a (où b! = A) devient x' = x(b-a)/(max-min) + min(-b+a)/(max-min) + a et peut être réduit à deux constantes de la forme x' = x*Part1 + Part2.

Voici une implémentation C # avec deux constructeurs: un pour former et un pour recharger une instance formée (par exemple, pour prendre en charge la persistance).

public class MinMaxColumnSpec
{
    /// <summary>
    /// To reduce repetitive computations, the min-max formula has been refactored so that the portions that remain constant are just computed once.
    /// This transforms the forumula from
    /// x' = (b-a)(x-min)/(max-min) + a
    /// into x' = x(b-a)/(max-min) + min(-b+a)/(max-min) + a
    /// which can be further factored into
    /// x' = x*Part1 + Part2
    /// </summary>
    public readonly double Part1, Part2;

    /// <summary>
    /// Use this ctor to train a new scaler.
    /// </summary>
    public MinMaxColumnSpec(double[] columnValues, int newMin = 0, int newMax = 1)
    {
        if (newMax <= newMin)
            throw new ArgumentOutOfRangeException("newMax", "newMax must be greater than newMin");

        var oldMax = columnValues.Max();
        var oldMin = columnValues.Min();

        Part1 = (newMax - newMin) / (oldMax - oldMin);
        Part2 = newMin + (oldMin * (newMin - newMax) / (oldMax - oldMin));
    }

    /// <summary>
    /// Use this ctor for previously-trained scalers with known constants.
    /// </summary>
    public MinMaxColumnSpec(double part1, double part2)
    {
        Part1 = part1;
        Part2 = part2;
    }

    public double Scale(double x) => (x * Part1) + Part2;
}
1
Kevin Fichter