Bilinen bir min ve maks değeri olan bir sayı aralığını ölçeklendirme


230

Bu yüzden bir dizi sayı almayı ve değerleri bir aralığa uyacak şekilde ölçeklendirmeyi anlamaya çalışıyorum. Bunu yapmak istememizin nedeni, bir java salıncak jpanelinde elips çizmeye çalışmamdır. Her bir elipsin yüksekliği ve genişliğinin 1-30 arasında bir aralıkta olmasını istiyorum. Benim veri kümesinden minimum ve maksimum değerleri bulmak yöntemleri var, ama çalışma zamanı kadar min ve max olmayacak. Bunu yapmanın kolay bir yolu var mı?

Yanıtlar:


507

Let Diyelim ki bir dizi ölçek istediğini söylüyorsun [min,max]için [a,b]. Tatmin edici bir (sürekli) fonksiyon arıyorsunuz

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

Sizin durumunuzda, a1 ve b30 olurdu, ancak daha basit bir şeyle başlayalım [min,max]ve aralığa eşlemeye çalışalım [0,1].

minBir işleve koyma ve 0'dan çıkma

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

Yani neredeyse istediğimiz bu. Ama aslında 1'i istediğimiz zaman maxbize vermek max - min, onu ölçeklendirmek zorunda kalacağız:

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

bizim istediğimiz bu. Bu yüzden bir çeviri ve ölçeklendirme yapmamız gerekiyor. Şimdi bunun yerine keyfi değerler elde etmek istiyorsak ave bbiraz daha karmaşık bir şeye ihtiyacımız var:

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

İçeri koyarak doğrulayabilir miniçin xşimdi verir ave de koyarak maxverir b.

Bunun (b-a)/(max-min), yeni aralığın boyutu ile orijinal aralığın boyutu arasında bir ölçeklendirme faktörü olduğunu da fark edebilirsiniz . Yani gerçekten biz ilk çevirmekte olduğunuz xtarafından -mindoğru faktörüne ölçeklendirmedir, ve daha sonra yeni asgari değerine yedeklemek tercüme a.

Bu yardımcı olur umarım.


Yardımın için minnettarım. Estetik olarak hoş görünme işini yapan bir çözüm buldum. Ancak daha doğru bir model vermek için mantığınızı uygulayacağım. Tekrar teşekkürler :)
user650271 16:11

4
Sadece bir hatırlatma: Model max != minaksi takdirde fonksiyon sonuçları belirsiz :) ile daha doğru olacaktır
marcoslhc

10
yeniden ölçeklendirilmiş değişkenimin orijinal dağıtımını korumasını sağlıyor mu?
Heisenberg

2
Bu doğrusal bir ölçeğin güzel bir uygulamasıdır. Bu kolayca logarighmik ölçeğe dönüştürülebilir mi?
tomexx

Çok açık bir açıklama. minNegatif ve maxpozitifse işe yarıyor mu , yoksa her ikisinin de pozitif olması gerekiyor mu?
Andrew

48

İşte kopyala yapıştır kolaylığı için bazı JavaScriptler (bu, tahrişin cevabıdır):

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

Bu şekilde uygulandığında, 10-50 aralığını 0-100 aralığına ölçeklendirme.

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

Düzenle:

Bunu uzun zaman önce cevapladığımı biliyorum, ama şimdi kullandığım daha temiz bir işlev:

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

Bu şekilde uygulanır:

[-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" e, "- 5000"]; bu durumda, yönteminize göre sayılar çok küçük oluyor. Ölçeğin (0,100) veya (-100,100) olması ve çıktılar arasındaki boşluk 0,5 (veya herhangi bir sayı) olması için herhangi bir yol var mı?

Lütfen arr [] için de senaryomu düşünün.

1
Biraz kenar durumu var, ancak dizi yalnızca bir değer veya aynı değere sahip birden çok kopya içeriyorsa bu ölür. Böylece [1] .scaleBetween (1, 100) ve [1,1,1] .scaleBetween (1,100) arasında çıktı NaN ile doldurulur.
Malabar Front

1
@MalabarFront, iyi gözlem. Ben bu durumda sonuç olup olmaması gerektiği tanımsız oluyor herhalde [1, 1, 1], [100, 100, 100]hatta [50.5, 50.5, 50.5]. Davayı koyabilirsiniz:if (max-min == 0) return this.map(num => (scaledMin+scaledMax)/2);
Charles Clayton

1
@CharlesClayton Harika, teşekkürler. Bu bir tedavi işe yarıyor!
Malabar Front

27

Kolaylık sağlamak için, Irritate'in algoritması bir Java biçimindedir. Hata kontrolü, kural dışı durum işleme ve gerektiğinde ince ayar yapın.

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

21

İşte böyle anlıyorum:


xBir aralıkta yüzde ne kadar

Let Diyelim ki bir aralığı vardır varsayalım 0için 100. Bu aralıktan rasgele bir sayı verildiğinde, bu aralıktan ne kadar "yüzde" yer alıyor? Bu oldukça basit 0olmalı 0%, 50olur , olur 50%ve 100olur 100%.

Şimdi, aralık neydi 20için 100? Yukarıdaki ile aynı mantığı uygulayamayız (100'e bölün) çünkü:

20 / 100

bize vermiyor 0( şimdi 20olmalı 0%). Bunu düzeltmek basit olmalı, sadece 0durum için pay yapmamız gerekiyor 20. Bunu çıkararak yapabiliriz:

(20 - 20) / 100

Ancak, bu 100artık işe yaramaz çünkü:

(100 - 20) / 100

bize vermiyor 100%. Yine, paydadan da çıkararak bunu düzeltebiliriz:

(100 - 20) / (100 - 20)

Bir xaralıkta % 'nin ne olduğunu bulmak için daha genel bir denklem şöyle olur:

(x - MIN) / (MAX - MIN)

Aralığı başka bir aralığa ölçeklendir

Artık bir sayının yüzde kaçının bir aralıkta olduğunu bildiğimize göre, sayıyı başka bir aralığa eşlemek için uygulayabiliriz. Bir örnek verelim.

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

Eski aralıkta bir numaramız olsaydı, sayı yeni aralıkta ne olurdu? Diyelim ki sayı 400. İlk olarak, yüzde kaçının 400eski aralıkta olduğunu bulun. Yukarıdaki denklemimizi uygulayabiliriz.

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

Yani, eski aralığın 400içinde yatıyor 25%. Sadece 25%yeni aralıkta hangi sayının olduğunu bulmamız gerekiyor . Ne 50%olduğunu düşünün [0, 20]. Doğru olur 10mu? Bu cevaba nasıl ulaştınız? Sadece yapabiliriz:

20 * 0.5 = 10

Ama ne olacak [10, 20]? Şimdiye kadar her şeyi kaydırmamız gerekiyor 10. Örneğin:

((20 - 10) * 0.5) + 10

daha genel bir formül şöyle olur:

((MAX - MIN) * PERCENT) + MIN

Ne olduğuna 25%dair orijinal örneğe [10, 20]:

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

Yani, 400aralıktaki [200, 1000]karşılık gelecek 12.5aralığında[10, 20]


TLDR

xEski aralıktan yeni aralığa eşlemek için:

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

1
Ben de böyle çalıştım. En zor kısım, bir sayının belirli bir aralıkta bulunduğu oranı bulmaktır. Her zaman tıpkı yüzde gibi [0, 1] aralığında olmalıdır, örneğin 0,5% 50'dir. Ardından, bu sayıyı yalnızca istediğiniz aralığa uyacak şekilde genişletmeniz / germeniz ve kaydırmanız gerekir.
SMUsamaShah

Adımları çok basit bir şekilde açıkladığınız için teşekkür ederiz.
RozzA

11

Bu çözüme rastladım ama bu gerçekten ihtiyacıma uymuyor. Bu yüzden d3 kaynak kodunda biraz kazdım. Şahsen d3.scale yaptığı gibi bunu tavsiye ederim.

Böylece burada etki alanını aralığa ölçeklersiniz. Avantajı, işaretleri hedef aralığınıza çevirebilmenizdir. Bu, bilgisayar ekranındaki y ekseni yukarıdan aşağıya ineceği için büyük değerlerin küçük bir y değerine sahip olması açısından yararlıdır.

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

Ve işte ne demek istediğimi görebileceğiniz test

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

"Avantaj, işaretleri hedef aralığınıza çevirebilmenizdir." Bunu anlamıyorum. Açıklayabilir misin? Döndürülen değerlerin d3 sürümünüzden ve sürümün üstünden farkını bulamıyorum (@irritate).
nimo23

Örnek 1 ve 2'yi hedef aralığınız anahtarlanmış olarak karşılaştırın
KIC

2

Irritate'in cevabını aldım ve sonraki hesaplamalar için hesaplama adımlarını en az sabitlere ayırarak en aza indirgemek için yeniden düzenledim. Motivasyon, bir ölçekleyicinin bir veri kümesi üzerinde eğitilmesine ve ardından yeni verilerde (bir ML algo için) çalıştırılmasına izin vermektir. Aslında, SciKit'in kullanımdaki Python için MinMaxScaler'ı önceden işleme tabi tutmaya benzer.

Böylece, x' = (b-a)(x-min)/(max-min) + a(burada b! = A) x' = x(b-a)/(max-min) + min(-b+a)/(max-min) + a, formdaki iki sabite indirgenebilecek hale gelirx' = x*Part1 + Part2 .

İşte iki kurucu ile bir C # uygulaması: biri eğitmek ve diğeri eğitimli bir örneği yeniden yüklemek için (örneğin, kalıcılığı desteklemek için).

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'un cevabına dayanarak, bazı JSDoc, ES6 tweaks'i dahil ettim ve orijinal yanıttaki yorumlardan gelen önerileri dahil ettim.

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

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.