알려진 최소값 및 최대 값으로 숫자 범위를 축소하는 방법


230

그래서 나는 숫자의 범위를 취하고 범위에 맞게 값을 축소하는 방법을 알아 내려고 노력하고 있습니다. 이 작업을 수행하려는 이유는 Java 스윙 jpanel에서 타원을 그리려고하기 때문입니다. 각 타원의 높이와 너비가 1-30 사이의 범위에 있기를 원합니다. 내 데이터 세트에서 최소값과 최대 값을 찾는 방법이 있지만 런타임까지 최소값과 최대 값이 없습니다. 이 작업을 수행하는 쉬운 방법이 있습니까?

답변:


507

범위 [min,max]를 로 조정하려는 경우를 가정 해 보겠습니다 [a,b]. 만족하는 (연속) 기능을 찾고 있습니다.

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

귀하의 경우 a1 b이 30이 될 것입니다.하지만 더 간단한 것으로 시작 [min,max]하여 range 에 매핑 하십시오 [0,1].

퍼팅 min함수에 0을 얻는 것은 달성 할 수

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

거의 우리가 원하는 것입니다. 그러나 실제로 1을 원할 때 우리 max에게 줄 것이다 max - min. 그래서 우리는 그것을 확장해야 할 것이다.

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

우리가 원하는 것입니다. 번역과 스케일링이 필요합니다. 이제 대신 aand의 임의의 값을 얻으려면 b조금 더 복잡한 것이 필요합니다.

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

당신은 퍼팅 확인할 수 있습니다 min에 대한 x지금하는 제공 a하고, 퍼팅 max수 있습니다 b.

(b-a)/(max-min)새 범위의 크기와 원래 범위의 크기 사이의 스케일링 요인 임을 알 수 있습니다 . 그래서 정말 우리가 처음 번역되어 x에서 -min올바른 요소에 확장, 다음이의 새로운 최소 값으로 백업 번역 a.

도움이 되었기를 바랍니다.


도와 주셔서 감사합니다. 나는 미적으로 기쁘게 보이는 일을하는 솔루션을 알아 냈습니다. 그러나 더 정확한 모델을 제공하기 위해 논리를 적용하겠습니다. 다시 한 번 감사드립니다 :)
user650271

4
알림 : 모델이 더 정확할 것입니다. max != min그렇지 않으면 함수 결과가 결정되지 않습니다. :)
marcoslhc

10
이렇게하면 재조정 된 변수가 원래 분포를 유지하게됩니까?
Heisenberg

2
이것은 선형 스케일의 훌륭한 구현입니다. 이것을 로그 스케일로 쉽게 변환 할 수 있습니까?
tomexx

매우 명확한 설명. min부정적이고 max긍정적 이면 효과 가 있습니까 , 아니면 둘 다 긍정적이어야합니까?
Andrew

48

복사-붙여 넣기 용이성을위한 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]


var arr = [ "-40000.00", "2", "3.000", "4.5825", "0.00008", "1000000000.00008", "0.02008", "100", "-5000", "-82.0000048", "0.02" , "0.005", "-3.0008", "5", "8", "600", "-1000", "-5000"]; 이 경우 방법에 따라 숫자가 너무 작아지고 있습니다. 스케일이 (0,100) 또는 (-100,100)이어야하고 출력 사이의 간격이 0.5 (또는 임의의 수) 여야합니다.

arr []에 대한 시나리오도 고려하십시오.

1
약간의 경우이지만 배열에 하나의 값만 포함하거나 같은 값의 여러 사본 만 포함하면 죽습니다. 따라서 [1] .scaleBetween (1, 100) 및 [1,1,1] .scaleBetween (1,100)은 모두 NaN으로 출력을 채 웁니다.
Malabar Front

1
@ MalabarFront, 좋은 관찰. 나는이 경우 결과가 될할지 여부를 정의되지 않은 가정 [1, 1, 1], [100, 100, 100], 또는 [50.5, 50.5, 50.5]. 당신은 사건을 넣을 수 있습니다 :if (max-min == 0) return this.map(num => (scaledMin+scaledMax)/2);
Charles Clayton

1
@CharlesClayton Fantastic, 감사합니다. 그것은 치료를 작동합니다!
Malabar Front

27

편의상 여기에 Java 형식의 Irritate 알고리즘이 있습니다. 오류 점검, 예외 처리를 추가하고 필요에 따라 조정하십시오.

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

21

내가 이해하는 방법은 다음과 같습니다.


x범위에 속하는 비율

범위가 0~ 이라고 가정합니다 100. 해당 범위의 임의의 숫자가 주어지면 해당 범위의 "퍼센트"는 무엇입니까? 이것은 아주 간단해야 0될 것 0%, 5050%100100%.

지금, 당신의 범위는 무엇 이었습니까 경우 20100? 다음과 같은 이유로 위와 동일한 논리를 적용 할 수 없습니다 (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

1
그것이 바로 내가 일한 방식입니다. 가장 중요한 부분은 숫자가 주어진 범위에 속하는 비율을 찾는 것입니다. 백분율과 마찬가지로 항상 [0, 1] 범위 내에 있어야합니다. 예를 들어 0.5는 50 %입니다. 다음으로 필요한 범위에 맞게이 숫자를 확장 / 확장 및 이동하면됩니다.
SMUsamaShah

매우 간단한 방법으로 단계를 설명해 주셔서 감사합니다. 위의 답변은 작동하지만 단계를 아는 것은 좋습니다.
RozzA

11

나는이 솔루션을 보았지만 이것은 실제로 내 필요에 맞지 않습니다. 그래서 나는 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);
    }
}

"장점은 목표 범위까지 표지판을 뒤집을 수 있다는 것입니다." 나는 이것을 이해하지 못한다. 설명 할 수 있습니까? d3 버전의 반환 값과 위의 버전 (@irritate)의 차이를 찾을 수 없습니다.
nimo23

목표 범위 전환 예제 1과 2 비교
KIC

2

나는 Irritate의 대답을 취하고 그것을 최소한의 상수로 인수 화하여 후속 계산을위한 계산 단계를 최소화하기 위해 리팩토링했습니다. 동기 부여는 스케일러가 하나의 데이터 세트에서 학습 된 다음 새 데이터 (ML 알고의 경우)에서 실행되도록하는 것입니다. 실제로 SciKit의 사전 처리 MinMaxScaler for 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;
}

2

Charles Clayton의 답변을 바탕으로 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; }

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.