Cách thu nhỏ phạm vi số với giá trị tối thiểu và tối thiểu đã biết


230

Vì vậy, tôi đang cố gắng tìm ra cách lấy một dãy số và thu nhỏ các giá trị để phù hợp với một phạm vi. Lý do muốn làm điều này là vì tôi đang cố gắng vẽ các hình elip trong một jpanel swing java. Tôi muốn chiều cao và chiều rộng của mỗi hình elip nằm trong khoảng từ 1-30. Tôi có các phương pháp tìm giá trị tối thiểu và tối đa từ tập dữ liệu của mình, nhưng tôi sẽ không có giá trị tối thiểu và tối đa cho đến khi chạy. Có cách nào làm dễ hơn không?

Câu trả lời:


507

Hãy nói rằng bạn muốn mở rộng phạm vi [min,max]tới [a,b]. Bạn đang tìm kiếm một hàm (liên tục) thỏa mãn

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

Trong trường hợp của bạn, asẽ là 1 và bsẽ là 30, nhưng hãy bắt đầu với một cái gì đó đơn giản hơn và cố gắng ánh xạ [min,max]vào phạm vi [0,1].

Đưa minvào một chức năng và nhận ra 0 có thể được thực hiện với

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

Vì vậy, đó gần như là những gì chúng ta muốn. Nhưng đưa vào maxsẽ cho chúng ta max - minkhi chúng ta thực sự muốn 1. Vì vậy, chúng ta sẽ phải mở rộng quy mô:

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

đó là những gì chúng ta muốn. Vì vậy, chúng ta cần phải làm một bản dịch và nhân rộng. Bây giờ nếu thay vào đó chúng ta muốn nhận các giá trị tùy ý ab, chúng ta cần một cái gì đó phức tạp hơn một chút:

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

Bạn có thể xác minh rằng việc đưa vào mincho xhiện nay mang lại cho a, và đưa vào maxcung cấp cho b.

Bạn cũng có thể nhận thấy rằng đó (b-a)/(max-min)là một hệ số tỷ lệ giữa kích thước của phạm vi mới và kích thước của phạm vi ban đầu. Vì vậy, thực sự đầu tiên chúng tôi dịch xbằng cách -minnhân rộng nó thành yếu tố chính xác, và sau đó dịch nó trở lại giá trị tối thiểu mới a.

Hi vọng điêu nay co ich.


Tôi đánh giá cao sự giúp đỡ của bạn. Tôi đã tìm ra một giải pháp làm công việc tìm kiếm thẩm mỹ. Tuy nhiên tôi sẽ áp dụng logic của bạn để đưa ra một mô hình chính xác hơn. Cảm ơn một lần nữa :)
user650271

4
Xin nhắc lại: Mô hình sẽ chính xác hơn max != minnếu không kết quả chức năng không xác định :)
marcoslhc

10
điều này có đảm bảo rằng biến thay đổi kích thước của tôi giữ lại phân phối ban đầu không?
Heisenberg

2
Đây là một thực hiện tốt đẹp của một quy mô tuyến tính. Điều này có thể dễ dàng chuyển đổi sang một thang logarighmic?
tomexx

Giải thích rất rõ ràng. Nó hoạt động nếu minlà tiêu cực và maxtích cực, hoặc cả hai phải tích cực?
Andrew

48

Đây là một số JavaScript để dễ dàng sao chép-dán (đây là câu trả lời gây khó chịu):

function scaleBetween(unscaledNum, minAllowed, maxAllowed, min, max) {
  return (maxAllowed - minAllowed) * (unscaledNum - min) / (max - min) + minAllowed;
}

Áp dụng như vậy, tỷ lệ phạm vi 10-50 đến phạm vi từ 0 đến 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

Biên tập:

Tôi biết tôi đã trả lời điều này từ lâu, nhưng đây là một chức năng sạch hơn mà tôi sử dụng bây giờ:

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);
}

Áp dụng như vậy:

[-4, 0, 5, 6, 9].scaleBetween(0, 100);

[0, 30.76923076923077, 69.23076923076923, 76.92307692307692, 100]


var Array = ["-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"]; trong trường hợp này, theo phương pháp của bạn, các con số đang trở nên quá nhỏ. Có cách nào để tỷ lệ phải là (0,100) hoặc (-100,100) và khoảng cách giữa các đầu ra phải là 0,5 (hoặc bất kỳ số nào).

Vui lòng xem xét kịch bản của tôi cho mảng [] quá.

1
Đó là một chút của trường hợp cạnh, nhưng điều này sẽ chết nếu mảng chỉ chứa một giá trị hoặc chỉ nhiều bản sao của cùng một giá trị. Vì vậy, [1] .scaleB between (1, 100) và [1,1,1] .scaleB between (1.100) đều lấp đầy đầu ra bằng NaN.
Mặt trận Malabar

1
@MalabarFront, quan sát tốt. Tôi cho rằng nó không xác định cho dù trong trường hợp đó kết quả nên được [1, 1, 1], [100, 100, 100]hoặc thậm chí [50.5, 50.5, 50.5]. Bạn có thể đặt trong trường hợp:if (max-min == 0) return this.map(num => (scaledMin+scaledMax)/2);
Charles Clayton

1
@CharlesClayton Tuyệt vời, cảm ơn. Đó là một điều trị!
Mặt trận Malabar

27

Để thuận tiện, đây là thuật toán của Irritate ở dạng Java. Thêm kiểm tra lỗi, xử lý ngoại lệ và chỉnh sửa khi cần thiết.

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;
    }
}

Kiểm thử:

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

Đây là cách tôi hiểu nó:


Bao nhiêu phần trăm xnằm trong một phạm vi

Giả sử bạn có một phạm vi từ 0đến 100. Cho một số tùy ý từ phạm vi đó, "phần trăm" từ phạm vi đó nằm ở đâu? Điều này sẽ khá đơn giản, 0sẽ 0%, 50sẽ 50%100sẽ 100%.

Bây giờ, những gì nếu phạm vi của bạn là 20để 100? Chúng tôi không thể áp dụng logic tương tự như trên (chia cho 100) vì:

20 / 100

không cho chúng tôi 0( 20nên là 0%bây giờ). Điều này nên đơn giản để sửa chữa, chúng ta chỉ cần làm tử số 0cho trường hợp 20. Chúng tôi có thể làm điều đó bằng cách trừ:

(20 - 20) / 100

Tuy nhiên, điều này không còn hiệu quả 100nữa vì:

(100 - 20) / 100

không cho chúng tôi 100%. Một lần nữa, chúng ta có thể khắc phục điều này bằng cách trừ đi mẫu số:

(100 - 20) / (100 - 20)

Một phương trình tổng quát hơn để tìm ra% xnằm trong một phạm vi sẽ là gì:

(x - MIN) / (MAX - MIN)

Phạm vi phạm vi đến phạm vi khác

Bây giờ chúng ta đã biết phần trăm của một số nằm trong một phạm vi, chúng ta có thể áp dụng nó để ánh xạ số đó sang một phạm vi khác. Chúng ta hãy đi qua một ví dụ.

old range = [200, 1000]
new range = [10, 20]

Nếu chúng ta có một số trong phạm vi cũ, thì số đó sẽ là gì trong phạm vi mới? Hãy nói số lượng là 400. Đầu tiên, hãy tìm ra bao nhiêu phần trăm 400trong phạm vi cũ. Chúng ta có thể áp dụng phương trình của chúng tôi ở trên.

(400 - 200) / (1000 - 200) = 0.25

Vì vậy, 400nằm trong 25%phạm vi cũ. Chúng ta chỉ cần tìm ra số nào là 25%của phạm vi mới. Hãy suy nghĩ về những gì 50%của [0, 20]là. Nó sẽ 10đúng chứ? Làm thế nào bạn đi đến câu trả lời? Vâng, chúng ta có thể làm:

20 * 0.5 = 10

Nhưng, những gì về từ [10, 20]? Chúng ta cần thay đổi mọi thứ 10ngay bây giờ. ví dụ:

((20 - 10) * 0.5) + 10

một công thức tổng quát hơn sẽ là:

((MAX - MIN) * PERCENT) + MIN

Để ví dụ ban đầu của những gì 25%của [10, 20]là:

((20 - 10) * 0.25) + 10 = 12.5

Vì vậy, 400trong phạm vi [200, 1000]sẽ ánh xạ tới 12.5trong phạm vi[10, 20]


TLD

Để ánh xạ xtừ phạm vi cũ sang phạm vi mới:

OLD PERCENT = (x - OLD MIN) / (OLD MAX - OLD MIN)
NEW X = ((NEW MAX - NEW MIN) * OLD PERCENT) + NEW MIN

1
Đó chính xác là cách tôi làm việc. Phần khó nhất là tìm ra tỷ lệ mà một số nằm trong một phạm vi nhất định. Nó phải luôn nằm trong phạm vi [0, 1] giống như tỷ lệ phần trăm, ví dụ 0,5 là 50%. Tiếp theo, bạn chỉ phải mở rộng / kéo dài và thay đổi số này để phù hợp với phạm vi yêu cầu của bạn.
SMUsamaShah

Cảm ơn bạn đã giải thích các bước theo cách rất đơn giản - copypasta ở trên câu trả lời / s hoạt động nhưng biết các bước chỉ là tuyệt vời.
RozzA

11

Tôi đã tìm thấy giải pháp này nhưng điều này không thực sự phù hợp với nhu cầu của tôi. Vì vậy, tôi đã đào một chút trong mã nguồn d3. Cá nhân tôi khuyên bạn nên làm điều đó như d3.scale.

Vì vậy, ở đây bạn quy mô tên miền đến phạm vi. Ưu điểm là bạn có thể lật các dấu hiệu đến phạm vi mục tiêu của bạn. Điều này rất hữu ích vì trục y trên màn hình máy tính từ trên xuống để các giá trị lớn có y nhỏ.

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));
    }
}

Và đây là bài kiểm tra mà bạn có thể thấy ý tôi là gì

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);
    }
}

"Ưu điểm là bạn có thể lật các dấu hiệu đến phạm vi mục tiêu của mình." Tôi không hiểu điều này Bạn có thể giải thích? Tôi không thể tìm thấy sự khác biệt của các giá trị được trả về từ phiên bản d3 của bạn và phiên bản ở trên (@irritate).
nimo23

So sánh ví dụ 1 và 2 phạm vi mục tiêu của bạn đã chuyển đổi
KIC

2

Tôi đã thực hiện câu trả lời của Irritate và tái cấu trúc nó để giảm thiểu các bước tính toán cho các tính toán tiếp theo bằng cách đưa nó vào các hằng số nhỏ nhất. Động lực là cho phép một người mở rộng được đào tạo trên một bộ dữ liệu và sau đó được chạy trên dữ liệu mới (đối với thuật toán ML). Về hiệu quả, nó giống như MinMaxScaler tiền xử lý của SciKit cho Python khi sử dụng.

Do đó, x' = (b-a)(x-min)/(max-min) + a(trong đó b! = A) trở thành x' = x(b-a)/(max-min) + min(-b+a)/(max-min) + acó thể rút gọn thành hai hằng số dưới dạngx' = x*Part1 + Part2 .

Đây là một triển khai C # với hai hàm tạo: một để đào tạo và một để tải lại một cá thể được đào tạo (ví dụ: để hỗ trợ tính bền bỉ).

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

Dựa trên phản hồi của Charles Clayton, tôi đã đưa vào một số điều chỉnh JSDoc, ES6 và các đề xuất kết hợp từ các nhận xét trong phản hồi ban đầu.

/**
 * 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; }

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.