Как уменьшить диапазон чисел с известным минимальным и максимальным значением - PullRequest
213 голосов
/ 14 марта 2011

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

Ответы [ 6 ]

479 голосов
/ 14 марта 2011

Допустим, вы хотите масштабировать диапазон от [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, нам нужно что-то немного более сложное:

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

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

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

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

39 голосов
/ 29 июля 2015

Вот некоторый 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]

26 голосов
/ 18 апреля 2014

Для удобства, вот алгоритм 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;
    }
}

Тестер:

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
16 голосов
/ 29 ноября 2017

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


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

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

Теперь, что если ваш диапазон был 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].Это было бы 10 верно?Как вы пришли к этому ответу?Ну, мы можем просто сделать:

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
10 голосов
/ 10 марта 2015

Я сталкивался с этим решением, но оно не совсем соответствует моим потребностям.Поэтому я немного покопался в исходном коде 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);
    }
}
1 голос
/ 08 апреля 2018

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

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

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

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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...