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

J'essaie donc de comprendre comment prendre une plage de nombres et mettre à l'échelle les valeurs pour s'adapter à une plage. La raison de vouloir le faire est que j'essaie de dessiner des ellipses dans un jpanel java swing. Je veux que la hauteur et la largeur de chaque ellipse soient dans une plage de dire 1-30. J'ai des méthodes qui trouvent les valeurs minimum et maximum de mon ensemble de données, mais je n'aurai pas le min et le max avant l'exécution. Est-il un moyen facile de faire cela?

186
demandé sur irritate 2011-03-14 08:07:03

6 réponses

Disons que vous voulez 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, {[6] } serait 1 et b serait 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 0 pourrait être accompli avec

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

Donc c'est presque ce que nous voulons. Mais mettre {[11] } nous donnerait max - min quand nous voulons réellement 1. Donc, nous allons avoir pour le mettre à l'échelle:

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

, Qui est ce que nous voulons. Nous devons donc faire une traduction et une mise à l'échelle. Maintenant, si à la place nous voulons 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 la mise en min pour x donne maintenant a, et la mise en max donne b.

Vous remarquerez peut-être également 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. Donc vraiment nous sommes les premiers traduire x par -min, l'adapter au facteur correct, puis le traduire à la nouvelle valeur minimale de a.

J'espère que cela aide.

432
répondu irritate 2011-03-14 05:49:25

Voici un peu de JavaScript pour la facilité de copier-coller (c'est la réponse d'irriter):

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

Appliqué comme ça, mise à l'échelle de la plage 10-50 à une plage comprise entre 0-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

Modifier:

Je sais que j'ai répondu à cela il y a longtemps, mais voici une fonction plus propre 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 ceci:

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

[0, 30.76923076923077, 69.23076923076923, 76.92307692307692, 100]

33
répondu Charles Clayton 2017-12-29 12:05:12

Pour plus de commodité, voici l'algorithme D'irriter sous forme Java. Ajouter la vérification des erreurs, la gestion des exceptions et modifier 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
25
répondu Java42 2018-03-06 21:54:37

Je suis tombé sur cette solution mais cela ne correspond pas vraiment à mon besoin. J'ai donc creusé un peu dans le code source d3. Personnellement, je recommanderais de le faire comme d3.l'échelle ne.

Donc, ici, vous mettez à l'échelle le domaine à la plage. L'avantage est que vous pouvez retourner les signes à votre plage cible. Ceci est utile car l'axe y sur un écran d'ordinateur descend de haut en bas, de sorte que 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);
    }
}
9
répondu KIC 2017-12-29 11:42:56

Voici comment je le comprends:


Quel pourcentage x se trouve dans une plage

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

Maintenant, et si votre plage était de 20 à 100? Nous ne pouvons pas appliquer la même logique que ci-dessus (diviser par 100) parce que:

20 / 100

Ne nous donne pas 0 (20 devrait être 0% maintenant). Cela devrait être facile à corriger, il suffit d'en 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 Car:

(100 - 20) / 100

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

(100 - 20) / (100 - 20)

Une équation plus généralisée pour savoir quel % x se trouve dans une plage serait être:

(x - MIN) / (MAX - MIN)

Plage D'échelle à une autre plage

Maintenant que nous savons quel pourcentage un nombre se trouve dans une plage, nous pouvons l'appliquer pour mapper le nombre à une autre plage. Nous allons passer par un exemple.

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

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

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

Donc, 400 se trouve dans {[34] } de l'ancien gamme. Nous avons juste besoin de comprendre quel nombre est 25% de la nouvelle gamme. Pensez à ce que 50% de [0, 20] est. Ce serait 10 droite? Comment êtes-vous arrivé à cette réponse? Eh bien, nous pouvons juste faire:

20 * 0.5 = 10

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

((20 - 10) * 0.5) + 10

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

((MAX - MIN) * PERCENT) + MIN

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

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

Donc, 400 dans la gamme [200, 1000] serait carte à 12.5 dans la plage [10, 20]


TLDR

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

OLD PERCENT = (x - OLD MIN) / (OLD MAX - OLD MIN)
NEW X = ((NEW MAX - NEW MIN) * OLD PERCENT) + NEW MIN
6
répondu Vic 2017-11-29 16:02:02

J'ai pris la réponse D'Irritate et l'ai refactorisée afin de minimiser les étapes de calcul pour les calculs ultérieurs en l'affacturant dans le moins de constantes. La motivation est de permettre à un scaler d'être formé sur un ensemble de données, puis d'être exécuté sur de nouvelles données (pour un ML algo). En effet, c'est un peu comme le prétraitement de SciKit MinMaxScaler pour Python dans l'utilisation.

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

Voici Un C# implémentation avec deux constructeurs: un pour former, et un pour recharger une instance formée (par exemple, pour supporter 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
répondu Kevin Fichter 2018-04-08 18:40:30