Как масштабировать диапазон чисел с известным минимальным и максимальным значением
Поэтому я пытаюсь выяснить, как принять диапазон чисел и масштабировать значения до подходящего диапазона. Причиной для этого является то, что я пытаюсь рисовать эллипсы в 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%
, 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]
. Было бы правильно? Как вы пришли к такому ответу? Ну, мы можем просто сделать:
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; }