Convierta un rango de números a otro rango, manteniendo la relación


257

Estoy tratando de convertir un rango de números a otro, manteniendo la relación. Las matemáticas no son mi punto fuerte.

Tengo un archivo de imagen donde los valores de los puntos pueden variar de -16000.00 a 16000.00, aunque el rango típico puede ser mucho menor. Lo que quiero hacer es comprimir estos valores en el rango entero 0-100, donde 0 es el valor del punto más pequeño y 100 es el valor del más grande. Todos los puntos intermedios deben mantener una relación relativa, aunque se pierda algo de precisión. Me gustaría hacer esto en Python, pero incluso un algoritmo general debería ser suficiente. Prefiero un algoritmo en el que se pueda ajustar el rango mínimo / máximo o cualquier rango (es decir, el segundo rango podría ser -50 a 800 en lugar de 0 a 100).


Gracias a ambos, le doy la respuesta a cletus porque él entró primero y un +1 a Jerry por responder a mi seguimiento.
SpliFF

2
Lo siento, Cletus, se lo doy a Jerry porque es nuevo y necesita los puntos.
SpliFF

2
¡Hola, eso es ageism! Jeje, j / k, no te preocupes. :)
cletus

77
¿Cómo escapó esta pregunta de la brigada de cerradores de preguntas de stackoverflow? ;)
thanikkal

Respuestas:


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

O un poco más legible:

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

O si desea proteger para el caso donde el rango anterior es 0 ( OldMin = OldMax ):

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

Tenga en cuenta que en este caso nos vemos obligados a elegir uno de los posibles valores de rango nuevos de forma arbitraria. Dependiendo del contexto, las opciones razonables podrían ser: NewMin( ver muestra ) NewMaxo(NewMin + NewMax) / 2


¿oldMax tiene que ser 16000 o puede ser el valor más alto en el antiguo conjunto de puntos (por ejemplo, 15034.00), ¿es importante la distinción?
SpliFF

55
Puede hacer lo que quiera ... tenga en cuenta que puede obtener resultados extraños si uno de los rangos es muy pequeño en relación con el otro (no está exactamente seguro, pero si hay más de una diferencia de factor 1000000 entre el tamaño de los rangos, asegúrese de que lo que realmente se comporta como esperas ... o aprender sobre la inexactitud punto flotante)
jerryjvl

2
Teniendo en cuenta la popularidad de esta respuesta, para un caso más general, debe considerar la posibilidad de OldMax == OldMin, que podría resultar en una división por cero.
usuario

3
Esto es asombroso ¿Hay un nombre matemático para esta conversión?
Tarik

2
Se llama conversión lineal, @Tarik
Rodrigo Borba

65

Esa es una conversión lineal simple.

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

Entonces, convertir 10000 en la escala de -16000 a 16000 a una nueva escala de 0 a 100 rinde:

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
Esto está mal. Debe restar Old Min del Old Value antes de la división.
SPWorley

20

En realidad, hay algunos casos que las respuestas anteriores se romperían. Como valores de entrada incorrectos, rango de entrada incorrecto, rangos de entrada / salida negativos.

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

Hay una condición, cuando todos los valores que está verificando son los mismos, donde el código de @ jerryjvl devolvería NaN.

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

5

No desenterré el BNF para esto, pero la documentación de Arduino tenía un gran ejemplo de la función y su desglose. Pude usar esto en Python simplemente agregando un cambio de nombre de definición para reasignar (porque el mapa está integrado) y eliminando los tipos de yeso y llaves (es decir, simplemente elimine todos los 'largos').

Original

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

Pitón

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

En la lista proporcionada por PenguinTD, no entiendo por qué se invierten los rangos, funciona sin tener que revertir los rangos. La conversión de rango lineal se basa en la ecuación lineal Y=Xm+n, donde my nse derivan de los rangos dados. En lugar de referirse a los rangos como miny max, sería mejor referirse a ellos como 1 y 2. Por lo tanto, la fórmula sería:

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

Donde Y=y1cuando X=x1y Y=y2cuando X=x2. x1, x2, y1Y y2se puede dar ninguna positiveo negativevalor. La definición de la expresión en una macro la hace más útil, luego se puede usar con cualquier nombre de argumento.

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

El floatreparto garantizaría la división de coma flotante en el caso en que todos los argumentos sean integervalores. Dependiendo de la aplicación, puede no ser necesario verificar los rangos x1=x2y y1==y2.


¡Gracias! aquí está la conversión de C #: float RangeConv(float input, float x1, float x2, float y1, float y2) { return (((input - x1) * (y2 - y1)) / (x2 - x1)) + y1; }
Zunair

2

Aquí hay algunas funciones cortas de Python para su facilidad de copiar y pegar, incluida una función para escalar una lista completa.

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]

Que se puede usar así:

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

[0.0, 50.0, 75.0, 100.0]

En mi caso, quería escalar una curva logarítmica, así:

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

[0.0, 21.533827903669653, 34.130309724299266, 43.06765580733931, 50.0]


1

Utilicé esta solución en un problema que estaba resolviendo en js, así que pensé en compartir la traducción. Gracias por la explicación y solución.

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

¡gracias! Una solución increíble y configurada como una función lista para funcionar
Combatir fuego con fuego

1

Variante C ++

Encontré la solución de PenguinTD útil, así que la porté a C ++ si alguien la necesita:

reasignación flotante (flotante x, flotante oMin, flotante oMax, flotante nMin, flotante 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

Puerto PHP

La solución de PenguinTD me pareció útil, así que la porté a PHP. ¡Ayudar a sí mismo!

/**
* =====================================
*              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

Aquí hay una versión de Javascript que devuelve una función que realiza el cambio de escala para rangos de origen y destino predeterminados, minimizando la cantidad de cómputo que se debe hacer cada vez.

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

Aquí hay un ejemplo del uso de esta función para escalar 0-1 en -0x80000000, 0x7FFFFFFF

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

0

Propuesta abreviada / simplificada

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

Wayne


1
Que hay NewRange/OldRangeaqui
Zunair

0

Yo personalmente uso la clase auxiliar que admite genéricos (compatible con 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

Este ejemplo convierte la posición actual de una canción en un rango de ángulo de 20 a 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

Lista de comprensión de una solución de línea

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

Versión más larga

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

Versión Java

¡Siempre funciona sin importar lo que alimentes!

Dejé todo expandido para que sea más fácil de seguir para aprender. El redondeo al final, por supuesto, es opcional.

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

}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.