如何缩小具有已知最小值和最大值的数字范围

问题描述 投票:207回答:6

因此,我试图找出如何获取一系列数字并将值缩小到适合范围。想要这样做的原因是我试图在java swing jpanel中绘制省略号。我希望每个椭圆的高度和宽度在1-30的范围内。我有从我的数据集中找到最小值和最大值的方法,但是直到运行时才会有min和max。是否有捷径可寻?

math range scaling max minimum
6个回答
479
投票

假设您想将范围[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所以我们必须扩展它:

        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

您可以验证为min投入x现在给a,并且放入maxb

您可能还会注意到(b-a)/(max-min)是新范围大小和原始范围大小之间的缩放系数。所以我们首先要通过x翻译-min,将其缩放到正确的因子,然后将其转换回a的新的最小值。

希望这可以帮助。


39
投票

这里有一些用于复制粘贴的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
投票

为方便起见,这里是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

16
投票

这是我理解的方式:


What percent does x lie in a range

假设您有从0100的范围。给定该范围内的任意数字,该范围内的“百分比”是多少?这应该很简单,0将是0%50将是50%100将是100%

现在,如果你的范围是20100怎么办?我们不能应用与上面相同的逻辑(除以100),因为:

20 / 100

不给我们020现在应该是0%)。这应该很容易修复,我们只需要为0的情况制作分子20。我们可以通过减去:

(20 - 20) / 100

但是,这对100不起作用了,因为:

(100 - 20) / 100

不给我们100%。同样,我们也可以通过从分母中减去它来解决这个问题:

(100 - 20) / (100 - 20)

找出%x在一个范围内的更一般化的等式将是:

(x - MIN) / (MAX - MIN)

Scale range to another range

现在我们知道一个数字在一个范围内的百分比,我们可以应用它来将数字映射到另一个范围。我们来看一个例子吧。

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
投票

我遇到了这个解决方案,但这并不适合我的需要。所以我在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
投票

我采用了Irritate的答案并重构了它,以便通过将其分解为最少的常数来最小化后续计算的计算步骤。动机是允许在一组数据上训练定标器,然后在新数据上运行(对于ML算法)。实际上,它与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的形式简化为两个常数。

这是一个带有两个构造函数的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;
}
© www.soinside.com 2019 - 2024. All rights reserved.