Как уменьшить диапазон чисел с известным минимальным и максимальным значением
Поэтому я пытаюсь выяснить, как взять диапазон чисел и масштабировать значения, чтобы соответствовать диапазону. Причина, по которой я хочу это сделать, заключается в том, что я пытаюсь рисовать эллипсы в java-качелях. Я хочу, чтобы высота и ширина каждого эллипса находились в диапазоне, скажем, 1-30. У меня есть методы, которые находят минимальные и максимальные значения из моего набора данных, но у меня не будет минимума и максимума до времени выполнения. Есть простой способ сделать это?
6 ответов
Допустим, вы хотите масштабировать диапазон [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
,
Надеюсь это поможет.
Вот некоторый 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
Вот как я это понимаю:
Какой процент делает 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
Я сталкивался с этим решением, но оно не совсем соответствует моим потребностям. Поэтому я немного покопался в исходном коде 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);
}
}
Я взял ответ Irritate и реорганизовал его, чтобы свести к минимуму вычислительные шаги для последующих вычислений, разделив его на наименьшее число констант. Мотивация состоит в том, чтобы позволить обучить скейлер на одном наборе данных, а затем запустить на новых данных (для алгоритма 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;
}
Иногда я нахожу вариант этого полезным.
- Обертывание функции масштабирования в классе, чтобы мне не нужно было передавать минимальные / максимальные значения при масштабировании одних и тех же диапазонов в нескольких местах
- Добавление двух небольших проверок, которые гарантируют, что значение результата останется в ожидаемом диапазоне.
Пример в JavaScript:
class Scaler {
constructor(inMin, inMax, outMin, outMax) {
this.inMin = inMin;
this.inMax = inMax;
this.outMin = outMin;
this.outMax = outMax;
}
scale(value) {
const result = (value - this.inMin) * (this.outMax - this.outMin) / (this.inMax - this.inMin) + this.outMin;
if (result < this.outMin) {
return this.outMin;
} else if (result > this.outMax) {
return this.outMax;
}
return result;
}
}
Этот пример вместе с версией на основе функций взят со страницы https://writingjavascript.com/scaling-values-between-two-ranges
Основываясь на ответе Чарльза Клейтона, я включил некоторые настройки JSDoc, ES6 и включил предложения из комментариев в исходный ответ.
/**
* Returns a scaled number within its source bounds to the desired target bounds.
* @param {number} n - Unscaled number
* @param {number} tMin - Minimum (target) bound to scale to
* @param {number} tMax - Maximum (target) bound to scale to
* @param {number} sMin - Minimum (source) bound to scale from
* @param {number} sMax - Maximum (source) bound to scale from
* @returns {number} The scaled number within the target bounds.
*/
const scaleBetween = (n, tMin, tMax, sMin, sMax) => {
return (tMax - tMin) * (n - sMin) / (sMax - sMin) + tMin;
}
if (Array.prototype.scaleBetween === undefined) {
/**
* Returns a scaled array of numbers fit to the desired target bounds.
* @param {number} tMin - Minimum (target) bound to scale to
* @param {number} tMax - Maximum (target) bound to scale to
* @returns {number} The scaled array.
*/
Array.prototype.scaleBetween = function(tMin, tMax) {
if (arguments.length === 1 || tMax === undefined) {
tMax = tMin; tMin = 0;
}
let sMax = Math.max(...this), sMin = Math.min(...this);
if (sMax - sMin == 0) return this.map(num => (tMin + tMax) / 2);
return this.map(num => (tMax - tMin) * (num - sMin) / (sMax - sMin) + tMin);
}
}
// ================================================================
// Usage
// ================================================================
let nums = [10, 13, 25, 28, 43, 50], tMin = 0, tMax = 100,
sMin = Math.min(...nums), sMax = Math.max(...nums);
// Result: [ 0.0, 7.50, 37.50, 45.00, 82.50, 100.00 ]
console.log(nums.map(n => scaleBetween(n, tMin, tMax, sMin, sMax).toFixed(2)).join(', '));
// Result: [ 0, 30.769, 69.231, 76.923, 100 ]
console.log([-4, 0, 5, 6, 9].scaleBetween(0, 100).join(', '));
// Result: [ 50, 50, 50 ]
console.log([1, 1, 1].scaleBetween(0, 100).join(', '));
.as-console-wrapper { top: 0; max-height: 100% !important; }