比率を維持しながら、数値範囲を別の範囲に変換する


257

比率を維持しながら、ある範囲の数値を別の範囲に変換しようとしています。数学は私の強みではありません。

ポイント値の範囲が-16000.00から16000.00の範囲の画像ファイルがありますが、通常の範囲はそれよりはるかに小さい場合があります。私がやりたいのは、これらの値を0〜100の整数の範囲に圧縮することです。0は最小点の値、100は最大値です。一部の精度が失われている場合でも、その間のすべてのポイントは相対的な比率を維持する必要があります。Pythonでこれを実行したいのですが、一般的なアルゴリズムでも十分です。私は最小/最大またはいずれかの範囲を調整できるアルゴリズムを好みます(つまり、2番目の範囲は0から100ではなく-50から800にすることができます)。


両方に感謝します。クレタスが最初に参加したので、クレタスに回答します。私のフォローアップに回答してジェリーに+1しました。
SpliFF 2009年

2
実際のすみませんクレタス、私は彼が新しくてポイントを必要とするのでジェリーにそれを与えています。
SpliFF 2009年

2
ねえ、それはエイジズムだ!へへ、j / k、心配しない。:)
cletus 2009年

7
この質問は、どのようにしてスタックオーバーフローの質問のクローザー旅団から逃れたのですか?;)
thanikkal 2015年

回答:


536
NewValue = (((OldValue - OldMin) * (NewMax - NewMin)) / (OldMax - OldMin)) + NewMin

またはもう少し読みやすい:

OldRange = (OldMax - OldMin)  
NewRange = (NewMax - NewMin)  
NewValue = (((OldValue - OldMin) * NewRange) / OldRange) + NewMin

または、古い範囲が0の場合(OldMin = OldMax)を保護する場合:

OldRange = (OldMax - OldMin)
if (OldRange == 0)
    NewValue = NewMin
else
{
    NewRange = (NewMax - NewMin)  
    NewValue = (((OldValue - OldMin) * NewRange) / OldRange) + NewMin
}

この場合、可能な新しい範囲値の1つを任意に選択する必要があることに注意してください。コンテキストに応じて、賢明な選択は次のとおりですNewMinサンプルを参照)、NewMaxまたは(NewMin + NewMax) / 2


oldMaxは16000である必要がありますか、それとも古いポイントセット(たとえば、15034.00など)の最高値にすることができますか?
SpliFF 2009年

5
好きなように作成できます...範囲の1つが他の範囲に比べて非常に小さい場合は正確な結果が得られない可能性があることに注意してください(正確にはわかりませんが、範囲、実際に期待どおりに動作することを確認してください...または浮動小数点の不正確さについて学習してください)
jerryjvl

2
この回答の人気を考慮して、より一般的なケースでは、OldMax == OldMinの可能性を検討する必要があります。これにより、ゼロによる除算が発生する可能性があります。
ユーザー

3
これは素晴らしいです。この変換の数学名はありますか?
Tarik

2
これは線形変換と呼ばれ、@ Tarik
Rodrigo Borba

65

これは単純な線形変換です。

new_value = ( (old_value - old_min) / (old_max - old_min) ) * (new_max - new_min) + new_min

したがって、-16000〜16000のスケールで10000を0〜100の新しいスケールに変換すると、次のようになります。

old_value = 10000
old_min = -16000
old_max = 16000
new_min = 0
new_max = 100

new_value = ( ( 10000 - -16000 ) / (16000 - -16000) ) * (100 - 0) + 0
          = 81.25

2
これは間違っています。除算する前に、古い値から古い最小値を引く必要があります。
SPWorley、2009年

20

実際には、上記の回答が壊れる場合があります。誤った入力値、誤った入力範囲、負の入力/出力範囲など。

def remap( x, oMin, oMax, nMin, nMax ):

    #range check
    if oMin == oMax:
        print "Warning: Zero input range"
        return None

    if nMin == nMax:
        print "Warning: Zero output range"
        return None

    #check reversed input range
    reverseInput = False
    oldMin = min( oMin, oMax )
    oldMax = max( oMin, oMax )
    if not oldMin == oMin:
        reverseInput = True

    #check reversed output range
    reverseOutput = False   
    newMin = min( nMin, nMax )
    newMax = max( nMin, nMax )
    if not newMin == nMin :
        reverseOutput = True

    portion = (x-oldMin)*(newMax-newMin)/(oldMax-oldMin)
    if reverseInput:
        portion = (oldMax-x)*(newMax-newMin)/(oldMax-oldMin)

    result = portion + newMin
    if reverseOutput:
        result = newMax - portion

    return result

#test cases
print remap( 25.0, 0.0, 100.0, 1.0, -1.0 ), "==", 0.5
print remap( 25.0, 100.0, -100.0, -1.0, 1.0 ), "==", -0.25
print remap( -125.0, -100.0, -200.0, 1.0, -1.0 ), "==", 0.5
print remap( -125.0, -200.0, -100.0, -1.0, 1.0 ), "==", 0.5
#even when value is out of bound
print remap( -20.0, 0.0, 100.0, 0.0, 1.0 ), "==", -0.2

9

チェックしているすべての値が同じである場合、@ jerryjvlのコードがNaNを返すという条件があります。

if (OldMin != OldMax && NewMin != NewMax):
    return (((OldValue - OldMin) * (NewMax - NewMin)) / (OldMax - OldMin)) + NewMin
else:
    return (NewMax + NewMin) / 2

5

私はBNFを掘り起こしませんでしたこのためをが、Arduinoのドキュメントには機能の優れた例とその内訳が含まれていました。これをPythonで使用するには、defの名前変更をリマップに追加し(マップは組み込みであるため)、型キャストと中括弧を削除します(つまり、すべての「long」を削除します)。

元の

long map(long x, long in_min, long in_max, long out_min, long out_max)
{
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

パイソン

def remap(x, in_min, in_max, out_min, out_max):
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min

https://www.arduino.cc/en/reference/map


2

PenguinTDが提供するリストでは、範囲が逆転する理由がわかりません。範囲を逆転しなくても機能します。線形範囲の変換は、線形方程式に基づいています。Y=Xm+nここでmnは指定された範囲から導出されます。範囲をminand として参照するのではなく、max1および2として参照することをお勧めします。したがって、式は次のようになります。

Y = (((X - x1) * (y2 - y1)) / (x2 - x1)) + y1

どこでY=y1するときX=x1、およびY=y2ときX=x2x1x2y1およびy2任意与えることができるpositive、またはnegative値。マクロで式を定義するとより便利になり、任意の引数名で使用できます。

#define RangeConv(X, x1, x2, y1, y2) (((float)((X - x1) * (y2 - y1)) / (x2 - x1)) + y1)

floatキャストは、すべての引数がある場合には浮動小数点除算を保証するinteger値。アプリケーションによっては、範囲x1=x2とを確認する必要がない場合がありy1==y2ます。


ありがとう! C#変換は次の float RangeConv(float input, float x1, float x2, float y1, float y2) { return (((input - x1) * (y2 - y1)) / (x2 - x1)) + y1; }
とおりです。– Zunair

2

リスト全体をスケーリングする関数など、コピーと貼り付けを簡単にするためのいくつかの短いPython関数を次に示します。

def scale_number(unscaled, to_min, to_max, from_min, from_max):
    return (to_max-to_min)*(unscaled-from_min)/(from_max-from_min)+to_min

def scale_list(l, to_min, to_max):
    return [scale_number(i, to_min, to_max, min(l), max(l)) for i in l]

これは次のように使用できます:

scale_list([1,3,4,5], 0, 100)

[0.0、50.0、75.0、100.0]

私の場合、次のように対数曲線をスケーリングしたいと思いました。

scale_list([math.log(i+1) for i in range(5)], 0, 50)

[0.0、21.533827903669653、34.130309724299266、43.06765580733931、50.0]


1

私はjsで解決していた問題でこの解決策を使用したので、翻訳を共有すると思いました。説明と解決策をありがとう。

function remap( x, oMin, oMax, nMin, nMax ){
//range check
if (oMin == oMax){
    console.log("Warning: Zero input range");
    return None;
};

if (nMin == nMax){
    console.log("Warning: Zero output range");
    return None
}

//check reversed input range
var reverseInput = false;
oldMin = Math.min( oMin, oMax );
oldMax = Math.max( oMin, oMax );
if (oldMin != oMin){
    reverseInput = true;
}

//check reversed output range
var reverseOutput = false;  
newMin = Math.min( nMin, nMax )
newMax = Math.max( nMin, nMax )
if (newMin != nMin){
    reverseOutput = true;
};

var portion = (x-oldMin)*(newMax-newMin)/(oldMax-oldMin)
if (reverseInput){
    portion = (oldMax-x)*(newMax-newMin)/(oldMax-oldMin);
};

var result = portion + newMin
if (reverseOutput){
    result = newMax - portion;
}

return result;
}

ありがとうございました!素晴らしいソリューションであり、すぐに使える機能として設定されています!
火との戦い

1

C ++バリアント

PenguinTDのSolutionが便利だと思ったので、必要に応じてC ++に移植しました。

float remap(float x、float oMin、float oMax、float nMin、float nMax){

//range check
if( oMin == oMax) {
    //std::cout<< "Warning: Zero input range";
    return -1;    }

if( nMin == nMax){
    //std::cout<<"Warning: Zero output range";
    return -1;        }

//check reversed input range
bool reverseInput = false;
float oldMin = min( oMin, oMax );
float oldMax = max( oMin, oMax );
if (oldMin == oMin)
    reverseInput = true;

//check reversed output range
bool reverseOutput = false;  
float newMin = min( nMin, nMax );
float newMax = max( nMin, nMax );
if (newMin == nMin)
    reverseOutput = true;

float portion = (x-oldMin)*(newMax-newMin)/(oldMax-oldMin);
if (reverseInput)
    portion = (oldMax-x)*(newMax-newMin)/(oldMax-oldMin);

float result = portion + newMin;
if (reverseOutput)
    result = newMax - portion;

return result; }

1

PHPポート

PenguinTDのソリューションが役に立ったので、PHPに移植しました。どうぞ食べて下さい!

/**
* =====================================
*              Remap Range            
* =====================================
* - Convert one range to another. (including value)
*
* @param    int $intValue   The value in the old range you wish to convert
* @param    int $oMin       The minimum of the old range
* @param    int $oMax       The maximum of the old range
* @param    int $nMin       The minimum of the new range
* @param    int $nMax       The maximum of the new range
*
* @return   float $fResult  The old value converted to the new range
*/
function remapRange($intValue, $oMin, $oMax, $nMin, $nMax) {
    // Range check
    if ($oMin == $oMax) {
        echo 'Warning: Zero input range';
        return false;
    }

    if ($nMin == $nMax) {
        echo 'Warning: Zero output range';
        return false;
    }

    // Check reversed input range
    $bReverseInput = false;
    $intOldMin = min($oMin, $oMax);
    $intOldMax = max($oMin, $oMax);
    if ($intOldMin != $oMin) {
        $bReverseInput = true;
    }

    // Check reversed output range
    $bReverseOutput = false;
    $intNewMin = min($nMin, $nMax);
    $intNewMax = max($nMin, $nMax);
    if ($intNewMin != $nMin) {
        $bReverseOutput = true;
    }

    $fRatio = ($intValue - $intOldMin) * ($intNewMax - $intNewMin) / ($intOldMax - $intOldMin);
    if ($bReverseInput) {
        $fRatio = ($intOldMax - $intValue) * ($intNewMax - $intNewMin) / ($intOldMax - $intOldMin);
    }

    $fResult = $fRatio + $intNewMin;
    if ($bReverseOutput) {
        $fResult = $intNewMax - $fRatio;
    }

    return $fResult;
}

1

これは、あらかじめ決められたソースと宛先の範囲の再スケーリングを行う関数を返すJavascriptバージョンで、毎回実行する必要がある計算量を最小限に抑えます。

// This function returns a function bound to the 
// min/max source & target ranges given.
// oMin, oMax = source
// nMin, nMax = dest.
function makeRangeMapper(oMin, oMax, nMin, nMax ){
    //range check
    if (oMin == oMax){
        console.log("Warning: Zero input range");
        return undefined;
    };

    if (nMin == nMax){
        console.log("Warning: Zero output range");
        return undefined
    }

    //check reversed input range
    var reverseInput = false;
    let oldMin = Math.min( oMin, oMax );
    let oldMax = Math.max( oMin, oMax );
    if (oldMin != oMin){
        reverseInput = true;
    }

    //check reversed output range
    var reverseOutput = false;  
    let newMin = Math.min( nMin, nMax )
    let newMax = Math.max( nMin, nMax )
    if (newMin != nMin){
        reverseOutput = true;
    }

    // Hot-rod the most common case.
    if (!reverseInput && !reverseOutput) {
        let dNew = newMax-newMin;
        let dOld = oldMax-oldMin;
        return (x)=>{
            return ((x-oldMin)* dNew / dOld) + newMin;
        }
    }

    return (x)=>{
        let portion;
        if (reverseInput){
            portion = (oldMax-x)*(newMax-newMin)/(oldMax-oldMin);
        } else {
            portion = (x-oldMin)*(newMax-newMin)/(oldMax-oldMin)
        }
        let result;
        if (reverseOutput){
            result = newMax - portion;
        } else {
            result = portion + newMin;
        }

        return result;
    }   
}

この関数を使用して0-1を-0x80000000、0x7FFFFFFFにスケーリングする例を次に示します

let normTo32Fn = makeRangeMapper(0, 1, -0x80000000, 0x7FFFFFFF);
let fs = normTo32Fn(0.5);
let fs2 = normTo32Fn(0);

0

ショートカット/簡略化された提案

 NewRange/OldRange = Handy multiplicand or HM
 Convert OldValue in OldRange to NewValue in NewRange = 
 (OldValue - OldMin x HM) + NewMin

ウェイン


1
NewRange/OldRangeここは何ですか?
Zunair

0

私はジェネリックをサポートするヘルパークラスを個人的に使用しています(Swift 3互換)

struct Rescale<Type : BinaryFloatingPoint> {
    typealias RescaleDomain = (lowerBound: Type, upperBound: Type)

    var fromDomain: RescaleDomain
    var toDomain: RescaleDomain

    init(from: RescaleDomain, to: RescaleDomain) {
        self.fromDomain = from
        self.toDomain = to
    }

    func interpolate(_ x: Type ) -> Type {
        return self.toDomain.lowerBound * (1 - x) + self.toDomain.upperBound * x;
    }

    func uninterpolate(_ x: Type) -> Type {
        let b = (self.fromDomain.upperBound - self.fromDomain.lowerBound) != 0 ? self.fromDomain.upperBound - self.fromDomain.lowerBound : 1 / self.fromDomain.upperBound;
        return (x - self.fromDomain.lowerBound) / b
    }

    func rescale(_ x: Type )  -> Type {
        return interpolate( uninterpolate(x) )
    }
}

0

この例では、曲の現在位置を20〜40度の角度範囲に変換します。

    /// <summary>
    /// This test converts Current songtime to an angle in a range. 
    /// </summary>
    [Fact]
    public void ConvertRangeTests()
    {            
       //Convert a songs time to an angle of a range 20 - 40
        var result = ConvertAndGetCurrentValueOfRange(
            TimeSpan.Zero, TimeSpan.FromMinutes(5.4),
            20, 40, 
            2.7
            );

        Assert.True(result == 30);
    }

    /// <summary>
    /// Gets the current value from the mixValue maxValue range.        
    /// </summary>
    /// <param name="startTime">Start of the song</param>
    /// <param name="duration"></param>
    /// <param name="minValue"></param>
    /// <param name="maxValue"></param>
    /// <param name="value">Current time</param>
    /// <returns></returns>
    public double ConvertAndGetCurrentValueOfRange(
                TimeSpan startTime,
                TimeSpan duration,
                double minValue,
                double maxValue,
                double value)
    {
        var timeRange = duration - startTime;
        var newRange = maxValue - minValue;
        var ratio = newRange / timeRange.TotalMinutes;
        var newValue = value * ratio;
        var currentValue= newValue + minValue;
        return currentValue;
    }

0

リスト内包ワンライナーソリューション

color_array_new = [int((((x - min(node_sizes)) * 99) / (max(node_sizes) - min(node_sizes))) + 1) for x in node_sizes]

長いバージョン

def colour_specter(waste_amount):
color_array = []
OldRange = max(waste_amount) - min(waste_amount)
NewRange = 99
for number_value in waste_amount:
    NewValue = int((((number_value - min(waste_amount)) * NewRange) / OldRange) + 1)
    color_array.append(NewValue)
print(color_array)
return color_array

0

Javaバージョン

何を与えても常に機能します!

私はすべてを広げたままにして、学習がより簡単にできるようにしました。もちろん、最後の丸めはオプションです。

    private long remap(long p, long Amin, long Amax, long Bmin, long Bmax ) {

    double deltaA = Amax - Amin;
    double deltaB = Bmax - Bmin;
    double scale  = deltaB / deltaA;
    double negA   = -1 * Amin;
    double offset = (negA * scale) + Bmin;
    double q      = (p * scale) + offset;
    return Math.round(q);

}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.