web-dev-qa-db-ja.com

最小値と最大値がわかっている数値範囲を縮小する方法

それで、私はどのように数値の範囲を取り、範囲に合うように値を縮小するかを考え出しています。これをやりたいのは、私がJava Swing jpanelで楕円を描こうとしているからです。各楕円の高さと幅を1〜30の範囲にする必要があります。データセットから最小値と最大値を見つけるメソッドがありますが、実行時まで最小値と最大値はわかりません。これを行う簡単な方法はありますか?

210
user650271

[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

だからそれは私たちが欲しいもののほとんどです。しかし、実際に1が欲しいときにmaxを入力するとmax - minになります。

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

それが私たちが欲しいものです。だから我々は翻訳とスケーリングをする必要があります。代わりにabの任意の値を取得したい場合は、もう少し複雑なものが必要です。

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

minxを入れるとaが得られ、maxを入れるとbが得られることを確認できます。

(b-a)/(max-min)は、新しい範囲のサイズと元の範囲のサイズとの間の倍率であることにも気付くかもしれません。したがって、実際には最初にx-minで変換し、それを正しい係数にスケーリングしてから、それを新しい最小値のaに戻すように変換しています。

お役に立てれば。

473
irritate

これはコピー&ペーストを簡単にするための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]

38
Charles Clayton

便宜上、これが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
26
Java42

これが私の理解の仕方です。


xが範囲内に何パーセントあるか

0から100までの範囲があるとしましょう。その範囲からの任意の数を考えて、その範囲からの何パーセントがそれにありますか?これはかなり単純なはずです、00%5050%、そして100100%になります。

さて、あなたの範囲が20から100までだったとしたら?以下の理由により、上記と同じロジック(100で割る)を適用することはできません。

20 / 100

0を教えてくれません(200%になるはずです)。これは簡単に直せるはずです、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
16
Vic

私はこの解決策に出くわしましたが、これは私の必要性に実際には合いません。そこで私は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);
    }
}
10
KIC

私はIrritateの答えを取り、それを最少の定数に分解することによってその後の計算のための計算ステップを最小にするためにそれをリファクタリングしました。動機はスケーラが1セットのデータで訓練されて、次に新しいデータで実行されるのを許容することです(ML algoのために)。実際には、それはSciKitのPython用前処理MinMaxScalerの使用法に非常に似ています。

したがって、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の形式で2つの定数に減らすことができます。

これは2つのコンストラクタを持つC#実装です。1つはトレーニング用で、もう1つはトレーニングされたインスタンスをリロードするためのものです(たとえば、持続性をサポートするため)。

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;
}
1
Kevin Fichter