Как масштабировать диапазон чисел с известным минимальным и максимальным значением

Поэтому я пытаюсь выяснить, как принять диапазон чисел и масштабировать значения до подходящего диапазона. Причиной для этого является то, что я пытаюсь рисовать эллипсы в java swing jpanel. Я хочу, чтобы высота и ширина каждого эллипса находились в диапазоне от 1 до 30. У меня есть методы, которые находят минимальные и максимальные значения из моего набора данных, но я не буду иметь min и max до времени выполнения. Есть простой способ сделать это?

    Предположим, вы хотите масштабировать диапазон [min,max] до [a,b] . Вы ищете (непрерывную) функцию, которая удовлетворяет

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

    В вашем случае a будет 1 и b будет 30, но начнем с чего-то более простого и попытаемся отобразить [min,max] в диапазон [0,1] .

    Включение min в функцию и выход из 0 может быть выполнено с помощью

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

    Так что это почти то, что мы хотим. Но вставка max дала бы нам max - min когда мы действительно хотим 1. Поэтому нам придется масштабировать его:

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

    это то, что мы хотим. Поэтому нам нужно сделать перевод и масштабирование. Теперь, если вместо этого мы хотим получить произвольные значения a и b , нам нужно что-то более сложное:

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

    Вы можете проверить, что вставка min для x теперь дает a , а put max дает b .

    Вы также можете заметить, что (ba)/(max-min) является коэффициентом масштабирования между размером нового диапазона и размером исходного диапазона. Итак, мы сначала переводим x на -min , масштабируя его до правильного коэффициента, а затем переводим обратно на новое минимальное значение a .

    Надеюсь это поможет.

    Вот несколько примеров JavaScript для удобства копирования (это раздражительный ответ):

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

    Применяется так, масштабируя диапазон 10-50 в диапазоне от 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

    Редактировать:

    Я знаю, что давно это ответил, но вот более чистая функция, которую я использую сейчас:

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

    Применяется так:

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

    [0, 30.76923076923077, 69.23076923076923, 76.92307692307692, 100]

    Для удобства здесь представлен алгоритм Irritate в форме Java. Добавьте проверку ошибок, обработку исключений и настройку по мере необходимости.

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

    Tester:

     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 

    Я наткнулся на это решение, но это не соответствует моей потребности. Поэтому я немного искал исходный код d3. Я лично рекомендовал бы сделать это, как d3.scale.

    Итак, здесь вы масштабируете домен в диапазоне. Преимущество в том, что вы можете перевернуть знаки в свой целевой диапазон. Это полезно, так как ось y на экране компьютера выходит сверху вниз, поэтому большие значения имеют небольшой 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)); } } 

    И вот тест, где вы можете видеть, что я имею в виду

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

    Вот как я это понимаю:


    Какой процент x лежит в диапазоне

    Предположим, что у вас есть диапазон от 0 до 100 . Учитывая произвольное число из этого диапазона, какой «процент» от этого диапазона он лежит? Это должно быть довольно просто, 0 будет 0% , 5050% а 100100% .

    Теперь, что, если ваш диапазон был от 20 до 100 ? Мы не можем применять ту же логику, что и выше (разделите на 100), потому что:

     20 / 100 

    не дает нам 0 ( 20 теперь должно быть 0% ). Это должно быть просто исправить, нам просто нужно сделать числитель 0 для случая 20 . Мы можем это сделать, вычитая:

     (20 - 20) / 100 

    Однако это больше не работает для 100 , потому что:

     (100 - 20) / 100 

    не дает нам 100% . Опять же, мы можем исправить это, вычитая из знаменателя:

     (100 - 20) / (100 - 20) 

    Более обобщенное уравнение для определения того, что% x лежит в диапазоне, будет:

     (x - MIN) / (MAX - MIN) 

    Диапазон масштабирования до другого диапазона

    Теперь, когда мы знаем, какой процент числа лежит в диапазоне, мы можем применить его для сопоставления номера с другим диапазоном. Давайте рассмотрим пример.

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

    Если у нас есть номер в старом диапазоне, какой номер будет в новом диапазоне? Скажем, это число 400 . Сначала выясните, какой процент 400 находится в пределах старого диапазона. Мы можем применить наше уравнение выше.

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

    Таким образом, 400 находится в 25% от старого диапазона. Нам просто нужно выяснить, какое количество составляет 25% от нового диапазона. Подумайте, что такое 50% от [0, 20] . Было бы правильно? Как вы пришли к такому ответу? Ну, мы можем просто сделать:

     20 * 0.5 = 10 

    Но, как насчет [10, 20] ? Нам нужно сдвинуть все на 10 сейчас. например:

     ((20 - 10) * 0.5) + 10 

    более обобщенная формула:

     ((MAX - MIN) * PERCENT) + MIN 

    К исходному примеру того, что 25% из [10, 20] :

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

    Таким образом, 400 в диапазоне [200, 1000] будут отображать 12.5 в диапазоне [10, 20]


    TLDR

    Чтобы отобразить x из старого диапазона в новый диапазон:

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

    Я принял решение Irritate и отредактировал его, чтобы свести к минимуму вычислительные шаги для последующих вычислений, разделив его на наименьшие константы. Мотивация заключается в том, чтобы позволить масштабируемому обучаться на одном наборе данных, а затем запускаться на новые данные (для ML algo). По сути, это очень похоже на предварительную обработку MinMaxScaler от SciKit для использования Python.

    Таким образом, x' = (ba)(x-min)/(max-min) + a (где b! = A) становится x' = x(ba)/(max-min) + min(-b+a)/(max-min) + a которая сводится к двум константам в виде x' = x*Part1 + Part2 .

    Вот реализация C # с двумя конструкторами: одна для обучения и одна для перезагрузки обученного экземпляра (например, для поддержки сохранения).

     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; } 
    Давайте будем гением компьютера.