Cómo escalar un rango de números con un valor mínimo y máximo conocido

Así que estoy tratando de descubrir cómo tomar un rango de números y escalar los valores para ajustarlos a un rango. La razón por la que quiero hacer esto es porque estoy tratando de dibujar elipsis en un jpanel de swing de java. Quiero que la altura y el ancho de cada elipse estén en un rango de, digamos, 1-30. Tengo métodos que encuentran los valores mínimo y máximo de mi conjunto de datos, pero no tendré el mínimo y máximo hasta el tiempo de ejecución. ¿Hay una forma fácil de hacer esto?

Digamos que quiere escalar un rango [min,max] a [a,b] . Está buscando una función (continua) que satisfaga

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

En su caso, a sería 1 b sería 30, pero comencemos con algo más simple e intentemos asignar [min,max] al rango [0,1] .

Poner min en una función y salir 0 se puede lograr con

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

Entonces eso es casi lo que queremos. Pero poner en max nos daría max - min cuando realmente queremos 1. Así que tendremos que escalarlo:

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

que es lo que queremos Entonces, tenemos que hacer una traducción y una escala. Ahora bien, si en cambio queremos obtener valores arbitrarios de b , necesitamos algo un poco más complicado:

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

Puede verificar que poner en min para x ahora da a , y poner en max da b .

También puede observar que (ba)/(max-min) Es un factor de escala entre el tamaño del nuevo rango y el tamaño del rango original. Entonces realmente estamos traduciendo x by -min , escalando hasta el factor correcto, y luego traduciéndolo de nuevo al nuevo valor mínimo de a .

Espero que esto ayude.

Aquí hay algunos JavaScript para copiar y pegar (esta es la respuesta de irritate):

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

Aplicado como tal, escalando el rango 10-50 a un rango 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

Editar:

Sé que respondí esto hace mucho tiempo, pero aquí hay una función más limpia que uso ahora:

 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); } 

Aplicado así:

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

[0, 30.76923076923077, 69.23076923076923, 76.92307692307692, 100]

Para su comodidad, aquí está el algoritmo de Irritate en forma de Java. Agregue la verificación de errores, manejo de excepciones y ajustes según sea necesario.

 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; } } 

Ensayador:

 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 

Me encontré con esta solución, pero esto realmente no se ajusta a mi necesidad. Así que investigué un poco en el código fuente d3. Yo personalmente recomendaría hacerlo como lo hace d3.scale.

Entonces aquí escalas el dominio al rango. La ventaja es que puedes voltear las señales a tu rango objective. Esto es útil ya que el eje y en la pantalla de una computadora va hacia arriba, de modo que los valores grandes tienen una y pequeña.

 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)); } } 

Y aquí está la prueba donde puedes ver a qué me refiero

 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); } } 

Así es como lo entiendo:


¿Qué porcentaje tiene x en un rango?

Supongamos que tiene un rango de 0 a 100 . Dado un número arbitrario de ese rango, ¿en qué porcentaje de ese rango se encuentra? Esto debería ser bastante simple, 0 sería 0% , 50 sería 50% y 100 sería 100% .

Ahora, ¿y si tu rango fuera de 20 a 100 ? No podemos aplicar la misma lógica que la anterior (dividir entre 100) porque:

 20 / 100 

no nos da 0 ( 20 debería ser 0% ahora). Esto debería ser fácil de arreglar, solo necesitamos hacer el numerador 0 para el caso de 20 . Podemos hacer eso restando:

 (20 - 20) / 100 

Sin embargo, esto ya no funciona para 100 porque:

 (100 - 20) / 100 

no nos da el 100% . Nuevamente, podemos solucionar esto restando también del denominador:

 (100 - 20) / (100 - 20) 

Una ecuación más generalizada para descubrir qué% x encuentra en un rango sería:

 (x - MIN) / (MAX - MIN) 

Rango de escala a otro rango

Ahora que sabemos qué porcentaje de un número se encuentra en un rango, podemos aplicarlo para asignar el número a otro rango. Veamos un ejemplo.

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

Si tenemos un número en el rango anterior, ¿cuál sería el número en el nuevo rango? Digamos que el número es 400 . Primero, averigüe qué porcentaje 400 está dentro del rango anterior. Podemos aplicar nuestra ecuación arriba.

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

Entonces, 400 mentiras en el 25% del rango anterior. Solo tenemos que averiguar qué número es el 25% del nuevo rango. Piensa en qué es el 50% de [0, 20] . Sería 10 ¿verdad? ¿Cómo llegaste a esa respuesta? Bueno, podemos hacer lo siguiente:

 20 * 0.5 = 10 

Pero, ¿qué pasa con [10, 20] ? Necesitamos cambiar todo por 10 ahora. p.ej:

 ((20 - 10) * 0.5) + 10 

una fórmula más generalizada sería:

 ((MAX - MIN) * PERCENT) + MIN 

Al ejemplo original de qué 25% de [10, 20] es:

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

Entonces, 400 en el rango [200, 1000] se mapearía a 12.5 en el rango [10, 20]


TLDR

Para asignar x del rango anterior al nuevo rango:

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

Tomé la respuesta de Irritate y la refactoricé para minimizar los pasos computacionales para cómputos posteriores al factorizarlo en la menor cantidad de constantes. La motivación es permitir que un escalador se entrene en un conjunto de datos, y luego se ejecute en nuevos datos (para un algoritmo ML). En efecto, es muy parecido al preprocesamiento de MinMaxScaler de SciKit para uso de Python.

Por lo tanto, x' = (ba)(x-min)/(max-min) + a (donde b! = A) se convierte en x' = x(ba)/(max-min) + min(-b+a)/(max-min) + a que se puede reducir a dos constantes en la forma x' = x*Part1 + Part2 .

Aquí hay una implementación de C # con dos constructores: uno para entrenar y otro para volver a cargar una instancia entrenada (por ejemplo, para admitir la persistencia).

 public class MinMaxColumnSpec { ///  /// 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' = (ba)(x-min)/(max-min) + a /// into x' = x(ba)/(max-min) + min(-b+a)/(max-min) + a /// which can be further factored into /// x' = x*Part1 + Part2 ///  public readonly double Part1, Part2; ///  /// Use this ctor to train a new scaler. ///  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)); } ///  /// Use this ctor for previously-trained scalers with known constants. ///  public MinMaxColumnSpec(double part1, double part2) { Part1 = part1; Part2 = part2; } public double Scale(double x) => (x * Part1) + Part2; }